Merge topic 'singleton-ChangeJson'

* changes:
  CreateChange: Don't use Factory
  ChangeJson: Don't use ChangesCollection
  ChangeJson: Make it semi stateless
  ChangeJson: Project control cache is dead; remove it
diff --git a/Documentation/config-project-config.txt b/Documentation/config-project-config.txt
index 474893d..43ede06 100644
--- a/Documentation/config-project-config.txt
+++ b/Documentation/config-project-config.txt
@@ -198,6 +198,41 @@
 documentation for a full list of available capabilities.
 
 
+[[branchOrder-section]]
+=== branchOrder section
+
+Defines a branch ordering which is used for backporting of changes.
+Backporting will be offered for a change (in the Gerrit UI) for all
+more stable branches where the change can merge cleanly.
+
+[[branchOrder.branch]]branchOrder.branch::
++
+A branch name, typically multiple values will be defined. The order of branch
+names in this section defines the branch order. The topmost is considered to be
+the least stable branch (typically the master branch) and the last one the
+most stable (typically the last maintained release branch).
+
+Example:
+
+----
+[branchOrder]
+  branch = master
+  branch = stable-2.9
+  branch = stable-2.8
+  branch = stable-2.7
+----
+
+The `branchOrder` section is inheritable. This is useful when multiple or all
+projects follow the same branch rules. A `branchOrder` section in a child
+project completely overrides any `branchOrder` section from a parent i.e. there
+is no merging of `branchOrder` sections. A present but empty `branchOrder`
+section removes all inherited branch order.
+
+Branches not listed in this section will not be included in the mergeability
+check. If the `branchOrder` section is not defined then the mergeability of a
+change into other branches will not be done.
+
+
 [[file-groups]]
 == The file +groups+
 
diff --git a/Documentation/json.txt b/Documentation/json.txt
index 6ab35f9..3624b3f 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -115,7 +115,7 @@
 
 isDraft:: Whether or not the patch set is a draft patch set.
 
-changeKind:: Kind of change uploaded.
+kind:: Kind of change uploaded.
 
   REWORK;; Nontrivial content changes.
 
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 6a3b34e..9ea4f13 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -30,6 +30,261 @@
   "2.7"
 ----
 
+[[list-caches]]
+=== List Caches
+--
+'GET /config/server/caches/'
+--
+
+Lists the caches of the server. Caches defined by plugins are included.
+
+The caller must be a member of a group that is granted the
+link:access-control.html#capability_viewCaches[View Caches] capability
+or the link:access-control.html#capability_administrateServer[
+Administrate Server] capability.
+
+As result a map of link:#cache-info[CacheInfo] entities is returned.
+
+The entries in the map are sorted by cache name.
+
+.Request
+----
+  GET /config/server/caches/ HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "accounts": {
+      "entries": {
+        "mem": 4
+      },
+      "average_get": "2.5ms",
+      "hit_ratio": {
+        "mem": 94
+      }
+    },
+    "accounts_byemail": {
+      "entries": {
+        "mem": 4
+      },
+      "average_get": "771.8us",
+      "hit_ratio": {
+        "mem": 95
+      }
+    },
+    "accounts_byname": {
+      "entries": {
+        "mem": 4
+      },
+      "hit_ratio": {
+        "mem": 100
+      }
+    },
+    "adv_bases": {
+      "entries": {},
+      "hit_ratio": {}
+    },
+    "change_kind": {
+      "type": "DISK",
+      "entries": {
+        "space": "0.00k"
+      },
+      "hit_ratio": {}
+    },
+    "changes": {
+      "entries": {},
+      "hit_ratio": {}
+    },
+    "conflicts": {
+      "type": "DISK",
+      "entries": {
+        "mem": 2,
+        "disk": 3,
+        "space": "2.75k"
+      },
+      "hit_ratio": {
+        "mem": 0,
+        "disk": 100
+      }
+    },
+    "diff": {
+      "type": "DISK",
+      "entries": {
+        "mem": 177,
+        "disk": 253,
+        "space": "170.97k"
+      },
+      "average_get": "1.1ms",
+      "hit_ratio": {
+        "mem": 67,
+        "disk": 100
+      }
+    },
+    "diff_intraline": {
+      "type": "DISK",
+      "entries": {
+        "mem": 1,
+        "disk": 1,
+        "space": "0.37k"
+      },
+      "average_get": "6.8ms",
+      "hit_ratio": {
+        "mem": 0
+      }
+    },
+    "git_tags": {
+      "type": "DISK",
+      "entries": {
+        "space": "0.00k"
+      },
+      "hit_ratio": {}
+    },
+    groups": {
+      "entries": {
+        "mem": 27
+      },
+      "average_get": "183.2us",
+      "hit_ratio": {
+        "mem": 12
+      }
+    },
+    "groups_byinclude": {
+      "entries": {},
+      "hit_ratio": {}
+    },
+    "groups_byname": {
+      "entries": {},
+      "hit_ratio": {}
+    },
+    "groups_byuuid": {
+      "entries": {
+        "mem": 25
+      },
+      "average_get": "173.4us",
+      "hit_ratio": {
+        "mem": 13
+      }
+    },
+    "groups_external": {
+      "entries": {},
+      "hit_ratio": {}
+    },
+    groups_members": {
+      "entries": {
+        "mem": 4
+      },
+      "average_get": "697.8us",
+      "hit_ratio": {
+        "mem": 82
+      }
+    },
+    "permission_sort": {
+      "entries": {
+        "mem": 16
+      },
+      "hit_ratio": {
+        "mem": 96
+      }
+    },
+    "plugin_resources": {
+      "entries": {
+        "mem": 2
+      },
+      "hit_ratio": {
+        "mem": 83
+      }
+    },
+    "project_list": {
+      "entries": {
+        "mem": 1
+      },
+      "average_get": "18.6ms",
+      "hit_ratio": {
+        "mem": 0
+      }
+    },
+    "projects": {
+      "entries": {
+        "mem": 35
+      },
+      "average_get": "8.6ms",
+      "hit_ratio": {
+        "mem": 99
+      }
+    },
+    "quota-repo_size": {
+      "type": "DISK",
+      "entries": {
+        "space": "0.00k"
+      },
+      "hit_ratio": {}
+    },
+    "sshkeys": {
+      "entries": {
+        "mem": 1
+      },
+      "average_get": "3.2ms",
+      "hit_ratio": {
+        "mem": 50
+      }
+    },
+    "web_sessions": {
+      "type": "DISK",
+      "entries": {
+        "mem": 1,
+        "disk": 2,
+        "space": "0.78k"
+      },
+      "hit_ratio": {
+        "mem": 82
+      }
+    }
+  }
+----
+
+[[get-cache]]
+=== Get Cache
+--
+'GET /config/server/caches/link:#cache-name[\{cache-name\}]'
+--
+
+Retrieves information about a cache.
+
+The caller must be a member of a group that is granted the
+link:access-control.html#capability_viewCaches[View Caches] capability
+or the link:access-control.html#capability_administrateServer[
+Administrate Server] capability.
+
+As result a link:#cache-info[CacheInfo] entity is returned.
+
+.Request
+----
+  GET /config/server/caches/projects HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json;charset=UTF-8
+
+  )]}'
+  {
+    "name": "projects",
+    "entries": {
+      "mem": 35
+    },
+    "average_get": " 8.6ms",
+    "hit_ratio": {
+      "mem": 99
+    }
+  }
+----
+
 [[list-capabilities]]
 === List Capabilities
 --
@@ -176,9 +431,48 @@
 ----
 
 
+[[ids]]
+== IDs
+
+[[cache-name]]
+=== \{cache-name\}
+The name of the cache.
+
+If the cache is defined by a plugin the cache name must include the
+plugin name: "<plugin-name>-<cache-name>".
+
+Gerrit core caches can optionally be prefixed with "gerrit":
+"gerrit-<cache-name>".
+
+
 [[json-entities]]
 == JSON Entities
 
+[[cache-info]]
+=== CacheInfo
+The `CacheInfo` entity contains information about a cache.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name           ||Description
+|`name`               |
+not set if returned in a map where the cache name is used as map key|
+The cache name. If the cache is defined by a plugin the cache name
+includes the plugin name: "<plugin-name>-<cache-name>".
+|`type`               |not set for in memory caches|
+The type of the cache (`MEM`: in memory cache, `DISK`: disk cache).
+|`entries`            ||
+Information about the entries in the cache as a
+link:#entries-info[EntriesInfo] entity.
+|`average_get`        |optional|
+The average duration of getting one entry from the cache. The value is
+returned with a standard time unit abbreviation (`ns`: nanoseconds,
+`us`: microseconds, `ms`: milliseconds, `s`: seconds).
+|`hit_ratio`          ||
+Information about the hit ratio as a link:#hit-ration-info[
+HitRatioInfo] entity.
+|==================================
+
 [[capability-info]]
 === CapabilityInfo
 The `CapabilityInfo` entity contains information about a capability.
@@ -191,6 +485,39 @@
 |`name`               |capability name
 |=================================
 
+[[entries-info]]
+=== EntriesInfo
+The `EntriesInfo` entity contains information about the entries in a
+cache.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name ||Description
+|`mem`      |optional|Number of cache entries that are held in memory.
+|`disk`     |optional|Number of cache entries on the disk. For non-disk
+caches this value is not set; for disk caches it is only set if there
+are entries in the cache.
+|`space`    |optional|
+The space that is consumed by the cache on disk. The value is returned
+with a unit abbreviation (`k`: kilobytes, `m`: megabytes,
+`g`: gigabytes). Only set for disk caches.
+|==================================
+
+[[hit-ration-info]]
+=== HitRatioInfo
+The `HitRatioInfo` entity contains information about the hit ratio of a
+cache.
+
+[options="header",width="50%",cols="1,^1,5"]
+|==================================
+|Field Name ||Description
+|`mem`      ||
+Hit ratio for cache entries that are held in memory (0 \<= value \<= 100).
+|`disk`     |optional|
+Hit ratio for cache entries that are held on disk (0 \<= value \<= 100).
+Only set for disk caches.
+|==================================
+
 [[top-menu-entry-info]]
 === TopMenuEntryInfo
 The `TopMenuEntryInfo` entity contains information about a top menu
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK
new file mode 100644
index 0000000..c89da30
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/BUCK
@@ -0,0 +1,6 @@
+include_defs('//gerrit-acceptance-tests/tests.defs')
+
+acceptance_tests(
+  srcs = glob(['*IT.java']),
+  labels = ['rest']
+)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
new file mode 100644
index 0000000..5a22b19
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/GetCacheIT.java
@@ -0,0 +1,74 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.server.config.ListCaches.CacheInfo;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+import java.io.IOException;
+
+public class GetCacheIT extends AbstractDaemonTest {
+
+  @Test
+  public void getCache() throws IOException {
+    RestResponse r = adminSession.get("/config/server/caches/accounts");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    CacheInfo result = newGson().fromJson(r.getReader(), CacheInfo.class);
+
+    assertEquals("accounts", result.name);
+    assertNull(result.type);
+    assertEquals(1, result.entries.mem.longValue());
+    assertNotNull(result.averageGet);
+    assertTrue(result.averageGet.endsWith("s"));
+    assertNull(result.entries.disk);
+    assertNull(result.entries.space);
+    assertTrue(result.hitRatio.mem >= 0);
+    assertTrue(result.hitRatio.mem <= 100);
+    assertNull(result.hitRatio.disk);
+
+    userSession.get("/config/server/version").consume();
+    r = adminSession.get("/config/server/caches/accounts");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    result = newGson().fromJson(r.getReader(), CacheInfo.class);
+    assertEquals(2, result.entries.mem.longValue());
+  }
+
+  @Test
+  public void getCache_Forbidden() throws IOException {
+    RestResponse r = userSession.get("/config/server/caches/accounts");
+    assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+  }
+
+  @Test
+  public void getCache_NotFound() throws IOException {
+    RestResponse r = adminSession.get("/config/server/caches/nonExisting");
+    assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
+  }
+
+  @Test
+  public void getCacheWithGerritPrefix() throws IOException {
+    RestResponse r = adminSession.get("/config/server/caches/gerrit-accounts");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
new file mode 100644
index 0000000..950b076
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ListCachesIT.java
@@ -0,0 +1,68 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.server.config.ListCaches.CacheInfo;
+import com.google.gson.reflect.TypeToken;
+
+import org.apache.http.HttpStatus;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class ListCachesIT extends AbstractDaemonTest {
+
+  @Test
+  public void listCaches() throws IOException {
+    RestResponse r = adminSession.get("/config/server/caches/");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    Map<String, CacheInfo> result =
+        newGson().fromJson(r.getReader(),
+            new TypeToken<Map<String, CacheInfo>>() {}.getType());
+
+    assertTrue(result.containsKey("accounts"));
+    CacheInfo accountsCacheInfo = result.get("accounts");
+    assertNull(accountsCacheInfo.type);
+    assertEquals(1, accountsCacheInfo.entries.mem.longValue());
+    assertNotNull(accountsCacheInfo.averageGet);
+    assertTrue(accountsCacheInfo.averageGet.endsWith("s"));
+    assertNull(accountsCacheInfo.entries.disk);
+    assertNull(accountsCacheInfo.entries.space);
+    assertTrue(accountsCacheInfo.hitRatio.mem >= 0);
+    assertTrue(accountsCacheInfo.hitRatio.mem <= 100);
+    assertNull(accountsCacheInfo.hitRatio.disk);
+
+    userSession.get("/config/server/version").consume();
+    r = adminSession.get("/config/server/caches/");
+    assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+    result = newGson().fromJson(r.getReader(),
+        new TypeToken<Map<String, CacheInfo>>() {}.getType());
+    assertEquals(2, result.get("accounts").entries.mem.longValue());
+  }
+
+  @Test
+  public void listCaches_Forbidden() throws IOException {
+    RestResponse r = userSession.get("/config/server/caches/");
+    assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+  }
+}
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index de7613d..652ed30 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -11,6 +11,7 @@
 import com.google.common.hash.Funnel;
 import com.google.common.hash.Funnels;
 import com.google.common.hash.PrimitiveSink;
+import com.google.gerrit.server.cache.PersistentCache;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.inject.TypeLiteral;
 
@@ -63,7 +64,8 @@
  *
  * @see H2CacheFactory
  */
-public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> {
+public class H2CacheImpl<K, V> extends AbstractLoadingCache<K, V> implements
+    PersistentCache {
   private static final Logger log = LoggerFactory.getLogger(H2CacheImpl.class);
 
   private final Executor executor;
@@ -156,6 +158,7 @@
     return mem.stats();
   }
 
+  @Override
   public DiskStats diskStats() {
     return store.diskStats();
   }
@@ -193,29 +196,6 @@
     }, delay, TimeUnit.MILLISECONDS);
   }
 
-  public static class DiskStats {
-    long size;
-    long space;
-    long hitCount;
-    long missCount;
-
-    public long size() {
-      return size;
-    }
-
-    public long space() {
-      return space;
-    }
-
-    public long hitCount() {
-      return hitCount;
-    }
-
-    public long requestCount() {
-      return hitCount + missCount;
-    }
-  }
-
   static class ValueHolder<V> {
     final V value;
     long created;
@@ -599,9 +579,8 @@
     }
 
     DiskStats diskStats() {
-      DiskStats d = new DiskStats();
-      d.hitCount = hitCount.get();
-      d.missCount = missCount.get();
+      long size = 0;
+      long space = 0;
       SqlHandle c = null;
       try {
         c = acquire();
@@ -613,8 +592,8 @@
               + " FROM data");
           try {
             if (r.next()) {
-              d.size = r.getLong(1);
-              d.space = r.getLong(2);
+              size = r.getLong(1);
+              space = r.getLong(2);
             }
           } finally {
             r.close();
@@ -628,7 +607,7 @@
       } finally {
         release(c);
       }
-      return d;
+      return new DiskStats(size, space, hitCount.get(), missCount.get());
     }
 
     private SqlHandle acquire() throws SQLException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java
new file mode 100644
index 0000000..62623ea
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/PersistentCache.java
@@ -0,0 +1,50 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.cache;
+
+public interface PersistentCache {
+
+  DiskStats diskStats();
+
+  public static class DiskStats {
+    private final long size;
+    private final long space;
+    private final long hitCount;
+    private final long missCount;
+
+    public DiskStats(long size, long space, long hitCount, long missCount) {
+      this.size = size;
+      this.space = space;
+      this.hitCount = hitCount;
+      this.missCount = missCount;
+    }
+
+    public long size() {
+      return size;
+    }
+
+    public long space() {
+      return space;
+    }
+
+    public long hitCount() {
+      return hitCount;
+    }
+
+    public long requestCount() {
+      return hitCount + missCount;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index e785736..f2c4adc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -24,12 +24,14 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.git.BranchOrderSection;
 import com.google.gerrit.server.git.CodeReviewCommit;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeException;
 import com.google.gerrit.server.git.strategy.SubmitStrategyFactory;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.NoSuchProjectException;
+import com.google.gerrit.server.project.ProjectCache;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -73,6 +75,7 @@
 
   private final TestSubmitType.Get submitType;
   private final GitRepositoryManager gitManager;
+  private final ProjectCache projectCache;
   private final SubmitStrategyFactory submitStrategyFactory;
   private final Provider<ReviewDb> db;
   private final ChangeIndexer indexer;
@@ -82,11 +85,13 @@
   @Inject
   Mergeable(TestSubmitType.Get submitType,
       GitRepositoryManager gitManager,
+      ProjectCache projectCache,
       SubmitStrategyFactory submitStrategyFactory,
       Provider<ReviewDb> db,
       ChangeIndexer indexer) {
     this.submitType = submitType;
     this.gitManager = gitManager;
+    this.projectCache = projectCache;
     this.submitStrategyFactory = submitStrategyFactory;
     this.db = db;
     this.indexer = indexer;
@@ -124,11 +129,17 @@
 
       if (otherBranches) {
         result.mergeableInto = new ArrayList<>();
-        for (Ref r : refs.values()) {
-          if (r.getName().startsWith(Constants.R_HEADS)
-              && !r.getName().equals(ref.getName())) {
-            if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, r)) {
-              result.mergeableInto.add(r.getName());
+        BranchOrderSection branchOrder =
+            projectCache.get(change.getProject()).getBranchOrderSection();
+        if (branchOrder != null) {
+          int prefixLen = Constants.R_HEADS.length();
+          for (String n : branchOrder.getMoreStable(ref.getName())) {
+            Ref other = refs.get(n);
+            if (other == null) {
+              continue;
+            }
+            if (isMergeable(change, ps, SubmitType.CHERRY_PICK, git, refs, other)) {
+              result.mergeableInto.add(other.getName().substring(prefixLen));
             }
           }
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java
new file mode 100644
index 0000000..9019f4d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CacheResource.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.common.cache.Cache;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.Provider;
+import com.google.inject.TypeLiteral;
+
+public class CacheResource extends ConfigResource {
+  public static final TypeLiteral<RestView<CacheResource>> CACHE_KIND =
+      new TypeLiteral<RestView<CacheResource>>() {};
+
+  private final String name;
+  private final Provider<Cache<?, ?>> cacheProvider;
+
+  public CacheResource(String pluginName, String cacheName, Provider<Cache<?, ?>> cacheProvider) {
+    this.name = cacheNameOf(pluginName, cacheName);
+    this.cacheProvider = cacheProvider;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Cache<?, ?> getCache() {
+    return cacheProvider.get();
+  }
+
+  public static String cacheNameOf(String plugin, String name) {
+    if ("gerrit".equals(plugin)) {
+      return name;
+    } else {
+      return plugin + "-" + name;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java
new file mode 100644
index 0000000..e288456
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CachesCollection.java
@@ -0,0 +1,88 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.common.cache.Cache;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@RequiresCapability(GlobalCapability.VIEW_CACHES)
+@Singleton
+public class CachesCollection implements
+    ChildCollection<ConfigResource, CacheResource> {
+
+  private final DynamicMap<RestView<CacheResource>> views;
+  private final Provider<ListCaches> list;
+  private final Provider<CurrentUser> self;
+  private final DynamicMap<Cache<?, ?>> cacheMap;
+
+  @Inject
+  CachesCollection(DynamicMap<RestView<CacheResource>> views,
+      Provider<ListCaches> list, Provider<CurrentUser> self,
+      DynamicMap<Cache<?, ?>> cacheMap) {
+    this.views = views;
+    this.list = list;
+    this.self = self;
+    this.cacheMap = cacheMap;
+  }
+
+  @Override
+  public RestView<ConfigResource> list() {
+    return list.get();
+  }
+
+  @Override
+  public CacheResource parse(ConfigResource parent, IdString id)
+      throws AuthException, ResourceNotFoundException {
+    CurrentUser user = self.get();
+    if (user instanceof AnonymousUser) {
+      throw new AuthException("Authentication required");
+    } else if (!user.isIdentifiedUser()) {
+      throw new ResourceNotFoundException();
+    } else if (!user.getCapabilities().canViewCaches()) {
+      throw new AuthException("not allowed to view caches");
+    }
+
+    String cacheName = id.get();
+    String pluginName = "gerrit";
+    int i = cacheName.lastIndexOf('-');
+    if (i != -1) {
+      pluginName = cacheName.substring(0, i);
+      cacheName = cacheName.length() > i + 1 ? cacheName.substring(i + 1) : "";
+    }
+
+    Provider<Cache<?, ?>> cacheProvider = cacheMap.byPlugin(pluginName).get(cacheName);
+    if (cacheProvider == null) {
+      throw new ResourceNotFoundException(id);
+    }
+    return new CacheResource(pluginName, cacheName, cacheProvider);
+  }
+
+  @Override
+  public DynamicMap<RestView<CacheResource>> views() {
+    return views;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetCache.java
new file mode 100644
index 0000000..53628cc
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetCache.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.config.ListCaches.CacheInfo;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GetCache implements RestReadView<CacheResource> {
+
+  @Override
+  public CacheInfo apply(CacheResource rsrc) {
+    return new CacheInfo(rsrc.getName(), rsrc.getCache());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java
new file mode 100644
index 0000000..6c5e9f1
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCaches.java
@@ -0,0 +1,163 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.config;
+
+import static com.google.gerrit.server.config.CacheResource.cacheNameOf;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheStats;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.cache.PersistentCache;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.Map;
+import java.util.TreeMap;
+
+@RequiresCapability(GlobalCapability.VIEW_CACHES)
+@Singleton
+public class ListCaches implements RestReadView<ConfigResource> {
+  private final DynamicMap<Cache<?, ?>> cacheMap;
+
+  @Inject
+  public ListCaches(DynamicMap<Cache<?, ?>> cacheMap) {
+    this.cacheMap = cacheMap;
+  }
+
+  @Override
+  public Map<String, CacheInfo> apply(ConfigResource rsrc) {
+    Map<String, CacheInfo> cacheInfos = new TreeMap<>();
+    for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
+      cacheInfos.put(cacheNameOf(e.getPluginName(), e.getExportName()),
+          new CacheInfo(e.getProvider().get()));
+    }
+    return cacheInfos;
+  }
+
+  public enum CacheType {
+    MEM, DISK;
+  }
+
+  public static class CacheInfo {
+    public String name;
+    public CacheType type;
+    public EntriesInfo entries;
+    public String averageGet;
+    public HitRatioInfo hitRatio;
+
+    public CacheInfo(Cache<?,?> cache) {
+      this(null, cache);
+    }
+
+    public CacheInfo(String name, Cache<?,?> cache) {
+      this.name = name;
+
+      CacheStats stat = cache.stats();
+
+      entries = new EntriesInfo();
+      entries.setMem(cache.size());
+
+      averageGet = duration(stat.averageLoadPenalty());
+
+      hitRatio = new HitRatioInfo();
+      hitRatio.setMem(stat.hitCount(), stat.requestCount());
+
+      if (cache instanceof PersistentCache) {
+        type = CacheType.DISK;
+        PersistentCache.DiskStats diskStats =
+            ((PersistentCache) cache).diskStats();
+        entries.setDisk(diskStats.size());
+        entries.setSpace(diskStats.space());
+        hitRatio.setDisk(diskStats.hitCount(), diskStats.requestCount());
+      }
+    }
+
+    private static String duration(double ns) {
+      if (ns < 0.5) {
+        return null;
+      }
+      String suffix = "ns";
+      if (ns >= 1000.0) {
+        ns /= 1000.0;
+        suffix = "us";
+      }
+      if (ns >= 1000.0) {
+        ns /= 1000.0;
+        suffix = "ms";
+      }
+      if (ns >= 1000.0) {
+        ns /= 1000.0;
+        suffix = "s";
+      }
+      return String.format("%4.1f%s", ns, suffix).trim();
+    }
+  }
+
+  public static class EntriesInfo {
+    public Long mem;
+    public Long disk;
+    public String space;
+
+    public void setMem(long mem) {
+      this.mem = mem != 0 ? mem : null;
+    }
+
+    public void setDisk(long disk) {
+      this.disk = disk != 0 ? disk : null;
+    }
+
+    public void setSpace(double value) {
+      space = bytes(value);
+    }
+
+    private static String bytes(double value) {
+      value /= 1024;
+      String suffix = "k";
+
+      if (value > 1024) {
+        value /= 1024;
+        suffix = "m";
+      }
+      if (value > 1024) {
+        value /= 1024;
+        suffix = "g";
+      }
+      return String.format("%1$6.2f%2$s", value, suffix).trim();
+    }
+  }
+
+  public static class HitRatioInfo {
+    public Integer mem;
+    public Integer disk;
+
+    public void setMem(long value, long total) {
+      mem = percent(value, total);
+    }
+
+    public void setDisk(long value, long total) {
+      disk = percent(value, total);
+    }
+
+    private static Integer percent(long value, long total) {
+      if (total <= 0) {
+        return null;
+      }
+      return (int) ((100 * value) / total);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
index 761b265..9d82640 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.config;
 
+import static com.google.gerrit.server.config.CacheResource.CACHE_KIND;
 import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
 import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
 import static com.google.gerrit.server.config.TopMenuResource.TOP_MENU_KIND;
@@ -24,9 +25,12 @@
 public class Module extends RestApiModule {
   @Override
   protected void configure() {
+    DynamicMap.mapOf(binder(), CACHE_KIND);
+    DynamicMap.mapOf(binder(), CAPABILITY_KIND);
     DynamicMap.mapOf(binder(), CONFIG_KIND);
     DynamicMap.mapOf(binder(), TOP_MENU_KIND);
-    DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+    child(CONFIG_KIND, "caches").to(CachesCollection.class);
+    get(CACHE_KIND).to(GetCache.class);
     child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
     child(CONFIG_KIND, "top-menus").to(TopMenuCollection.class);
     get(CONFIG_KIND, "version").to(GetVersion.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java
new file mode 100644
index 0000000..c447d31
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BranchOrderSection.java
@@ -0,0 +1,60 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import com.google.common.collect.ImmutableList;
+
+import org.eclipse.jgit.lib.Constants;
+
+import java.util.List;
+
+public class BranchOrderSection {
+
+  /**
+   * Branch names ordered from least to the most stable.
+   *
+   * Typically the order will be like: master, stable-M.N, stable-M.N-1, ...
+   */
+  private final ImmutableList<String> order;
+
+  public BranchOrderSection(String[] order) {
+    if (order.length == 0) {
+      this.order = ImmutableList.of();
+    } else {
+      ImmutableList.Builder<String> builder = ImmutableList.builder();
+      for (String b : order) {
+        builder.add(fullName(b));
+      }
+      this.order = builder.build();
+    }
+  }
+
+  private static String fullName(String branch) {
+    if (branch.startsWith(Constants.R_HEADS)) {
+      return branch;
+    } else {
+      return Constants.R_HEADS + branch;
+    }
+  }
+
+  public List<String> getMoreStable(String branch) {
+    int i = order.indexOf(fullName(branch));
+    if (0 <= i) {
+      return order.subList(i + 1, order.size());
+    } else {
+      return ImmutableList.of();
+    }
+  }
+}
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 d5dae8e..c4c1b7f 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
@@ -95,6 +95,9 @@
   private static final String ACCOUNTS = "accounts";
   private static final String KEY_SAME_GROUP_VISIBILITY = "sameGroupVisibility";
 
+  private static final String BRANCH_ORDER = "branchOrder";
+  private static final String BRANCH = "branch";
+
   private static final String CONTRIBUTOR_AGREEMENT = "contributor-agreement";
   private static final String KEY_ACCEPTED = "accepted";
   private static final String KEY_REQUIRE_CONTACT_INFORMATION = "requireContactInformation";
@@ -152,6 +155,7 @@
   private AccountsSection accountsSection;
   private Map<AccountGroup.UUID, GroupReference> groupsByUUID;
   private Map<String, AccessSection> accessSections;
+  private BranchOrderSection branchOrderSection;
   private Map<String, ContributorAgreement> contributorAgreements;
   private Map<String, NotifyConfig> notifySections;
   private Map<String, LabelType> labelSections;
@@ -242,6 +246,10 @@
     return sort(accessSections.values());
   }
 
+  public BranchOrderSection getBranchOrderSection() {
+    return branchOrderSection;
+  }
+
   public void remove(AccessSection section) {
     if (section != null) {
       accessSections.remove(section.getName());
@@ -420,6 +428,7 @@
     loadAccountsSection(rc, groupsByName);
     loadContributorAgreements(rc, groupsByName);
     loadAccessSections(rc, groupsByName);
+    loadBranchOrderSection(rc);
     loadNotifySections(rc, groupsByName);
     loadLabelSections(rc);
     loadCommentLinkSections(rc);
@@ -570,6 +579,13 @@
     }
   }
 
+  private void loadBranchOrderSection(Config rc) {
+    if (rc.getSections().contains(BRANCH_ORDER)) {
+      branchOrderSection = new BranchOrderSection(
+          rc.getStringList(BRANCH_ORDER, null, BRANCH));
+    }
+  }
+
   private List<PermissionRule> loadPermissionRules(Config rc, String section,
       String subsection, String varName,
       Map<String, GroupReference> groupsByName,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index f6b96d7..715419d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -40,6 +40,7 @@
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.git.BranchOrderSection;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.git.ProjectLevelConfig;
@@ -445,6 +446,16 @@
     return ImmutableList.copyOf(cls.values());
   }
 
+  public BranchOrderSection getBranchOrderSection() {
+    for (ProjectState s : tree()) {
+      BranchOrderSection section = s.getConfig().getBranchOrderSection();
+      if (section != null) {
+        return section;
+      }
+    }
+    return null;
+  }
+
   public ThemeInfo getTheme() {
     ThemeInfo theme = this.theme;
     if (theme == null) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
index 7f9e5db..3d568e1 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/CacheCommand.java
@@ -38,7 +38,7 @@
     if ("gerrit".equals(plugin)) {
       return name;
     } else {
-      return plugin + "." + name;
+      return plugin + "-" + name;
     }
   }
 }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
index 397120f..00ccae2 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowCaches.java
@@ -16,20 +16,21 @@
 
 import static com.google.gerrit.sshd.CommandMetaData.Mode.MASTER_OR_SLAVE;
 
-import com.google.common.cache.Cache;
-import com.google.common.cache.CacheStats;
-import com.google.common.collect.Maps;
+import com.google.common.base.Strings;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.events.LifecycleListener;
-import com.google.gerrit.extensions.registration.DynamicMap;
-import com.google.gerrit.server.cache.h2.H2CacheImpl;
+import com.google.gerrit.server.config.ConfigResource;
+import com.google.gerrit.server.config.ListCaches;
+import com.google.gerrit.server.config.ListCaches.CacheInfo;
+import com.google.gerrit.server.config.ListCaches.CacheType;
 import com.google.gerrit.server.config.SitePath;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.git.WorkQueue.Task;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
 import com.google.gerrit.sshd.SshDaemon;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -52,13 +53,12 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.Map;
-import java.util.SortedMap;
 
 /** Show the current cache states. */
 @RequiresCapability(GlobalCapability.VIEW_CACHES)
 @CommandMetaData(name = "show-caches", description = "Display current cache statistics",
   runsAt = MASTER_OR_SLAVE)
-final class ShowCaches extends CacheCommand {
+final class ShowCaches extends SshCommand {
   private static volatile long serverStarted;
 
   static class StartupListener implements LifecycleListener {
@@ -88,6 +88,9 @@
   @SitePath
   private File sitePath;
 
+  @Inject
+  private Provider<ListCaches> listCaches;
+
   @Option(name = "--width", aliases = {"-w"}, metaVar = "COLS", usage = "width of output table")
   private int columns = 80;
   private int nw;
@@ -145,23 +148,10 @@
     }
     stdout.print("+---------------------+---------+---------+\n");
 
-    Map<String, H2CacheImpl<?, ?>> disks = Maps.newTreeMap();
-    printMemoryCaches(disks, sortedCoreCaches());
-    printMemoryCaches(disks, sortedPluginCaches());
-    for (Map.Entry<String, H2CacheImpl<?, ?>> entry : disks.entrySet()) {
-      H2CacheImpl<?, ?> cache = entry.getValue();
-      CacheStats stat = cache.stats();
-      H2CacheImpl.DiskStats disk = cache.diskStats();
-      stdout.print(String.format(
-          "D %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
-          entry.getKey(),
-          count(cache.size()),
-          count(disk.size()),
-          bytes(disk.space()),
-          duration(stat.averageLoadPenalty()),
-          percent(stat.hitCount(), stat.requestCount()),
-          percent(disk.hitCount(), disk.requestCount())));
-    }
+    Collection<CacheInfo> caches = getCaches();
+    printMemoryCoreCaches(caches);
+    printMemoryPluginCaches(caches);
+    printDiskCaches(caches);
     stdout.print('\n');
 
     if (gc) {
@@ -181,46 +171,62 @@
     stdout.flush();
   }
 
-  private void printMemoryCaches(
-      Map<String, H2CacheImpl<?, ?>> disks,
-      Map<String, Cache<?,?>> caches) {
-    for (Map.Entry<String, Cache<?,?>> entry : caches.entrySet()) {
-      Cache<?,?> cache = entry.getValue();
-      if (cache instanceof H2CacheImpl) {
-        disks.put(entry.getKey(), (H2CacheImpl<?,?>)cache);
-        continue;
+  private Collection<CacheInfo> getCaches() {
+    Map<String, CacheInfo> caches = listCaches.get().apply(new ConfigResource());
+    for (Map.Entry<String, CacheInfo> entry : caches.entrySet()) {
+      CacheInfo cache = entry.getValue();
+      if (cache.type == null) {
+        cache.type = CacheType.MEM;
       }
-      CacheStats stat = cache.stats();
-      stdout.print(String.format(
-          "  %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
-          entry.getKey(),
-          count(cache.size()),
-          "",
-          "",
-          duration(stat.averageLoadPenalty()),
-          percent(stat.hitCount(), stat.requestCount()),
-          ""));
+      cache.name = entry.getKey();
+    }
+    return caches.values();
+  }
+
+  private void printMemoryCoreCaches(Collection<CacheInfo> caches) {
+    for (CacheInfo cache : caches) {
+      if (!cache.name.contains("-") && CacheType.MEM.equals(cache.type)) {
+        printCache(cache);
+      }
     }
   }
 
-  private Map<String, Cache<?, ?>> sortedCoreCaches() {
-    SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
-    for (Map.Entry<String, Provider<Cache<?, ?>>> entry :
-        cacheMap.byPlugin("gerrit").entrySet()) {
-      m.put(cacheNameOf("gerrit", entry.getKey()), entry.getValue().get());
-    }
-    return m;
-  }
-
-  private Map<String, Cache<?, ?>> sortedPluginCaches() {
-    SortedMap<String, Cache<?, ?>> m = Maps.newTreeMap();
-    for (DynamicMap.Entry<Cache<?, ?>> e : cacheMap) {
-      if (!"gerrit".equals(e.getPluginName())) {
-        m.put(cacheNameOf(e.getPluginName(), e.getExportName()),
-            e.getProvider().get());
+  private void printMemoryPluginCaches(Collection<CacheInfo> caches) {
+    for (CacheInfo cache : caches) {
+      if (cache.name.contains("-") && CacheType.MEM.equals(cache.type)) {
+        printCache(cache);
       }
     }
-    return m;
+  }
+
+  private void printDiskCaches(Collection<CacheInfo> caches) {
+    for (CacheInfo cache : caches) {
+      if (CacheType.DISK.equals(cache.type)) {
+        printCache(cache);
+      }
+    }
+  }
+
+  private void printCache(CacheInfo cache) {
+    stdout.print(String.format(
+        "%1s %-"+nw+"s|%6s %6s %7s| %7s |%4s %4s|\n",
+        CacheType.DISK.equals(cache.type) ? "D" : "",
+        cache.name,
+        nullToEmpty(cache.entries.mem),
+        nullToEmpty(cache.entries.disk),
+        Strings.nullToEmpty(cache.entries.space),
+        Strings.nullToEmpty(cache.averageGet),
+        formatAsPercent(cache.hitRatio.mem),
+        formatAsPercent(cache.hitRatio.disk)
+      ));
+  }
+
+  private static String nullToEmpty(Long l) {
+    return l != null ? String.valueOf(l) : "";
+  }
+
+  private static String formatAsPercent(Integer i) {
+    return i != null ? String.valueOf(i) + "%" : "";
   }
 
   private void memSummary() {
@@ -358,39 +364,4 @@
     }
     return String.format("%1$6.2f%2$s", value, suffix);
   }
-
-  private String count(long cnt) {
-    if (cnt == 0) {
-      return "";
-    }
-    return String.format("%6d", cnt);
-  }
-
-  private String duration(double ns) {
-    if (ns < 0.5) {
-      return "";
-    }
-    String suffix = "ns";
-    if (ns >= 1000.0) {
-      ns /= 1000.0;
-      suffix = "us";
-    }
-    if (ns >= 1000.0) {
-      ns /= 1000.0;
-      suffix = "ms";
-    }
-    if (ns >= 1000.0) {
-      ns /= 1000.0;
-      suffix = "s ";
-    }
-    return String.format("%4.1f%s", ns, suffix);
-  }
-
-  private String percent(final long value, final long total) {
-    if (total <= 0) {
-      return "";
-    }
-    final long pcent = (100 * value) / total;
-    return String.format("%3d%%", (int) pcent);
-  }
 }