Calculate repository disk usage by adding size of git objects

Originally, the size of a repository is computed by adding up the size
of the files on disk. This calculation can be larger than the size of
the objects in the repository in some cases, like for example:

 * Repository folders are mounted on NFS volumes so that files in .nfs
   folders are added in quota.
 * Size of bitmap files generated by GC are added in quota.
 * Garbage files in the repository are added in quota.

This change adds the option of calculating the size of a repository by
adding the size of the git objects in the repository. Even if this
calculation have some limitations (see below), it can give a more
accurate approximation in the cases mentioned before.

This option is also useful to avoid blocking access to a dirty
repository, e.g., when GC does not run in time.

---Limitations & Q&A---
Tested with JGit version 4.1.2.201602141800-r

 * Q: Does it consider only reachable objects?
   A: No. Any git object present in the repository is accounted in the
      final calculation.

 * Q: If an object is present in more than one pack file, is its size
      computed only once?
   A: No. Given JGit calculates the size of the packed objects as the
      size of the pack files, if an object is present in two different
      packs it will be counted twice.

 * Q: If an object is present in a pack file and also as a loose object,
      is its size computed only once?
   A: No. JGit calculates the size of packed objects as the size of pack
      files and the size of loose objects as the sum of the sizes of all
      files present in the 'objects' directory excluding 'pack' and
      'info' folders. So, if an object is present in a pack file and also
      as a loose object it will be counted twice.

Change-Id: Icc5bc6fdf8fa5c4635eabc1cd61870890e5277c9
diff --git a/src/main/java/com/googlesource/gerrit/plugins/quota/MaxRepositorySizeQuota.java b/src/main/java/com/googlesource/gerrit/plugins/quota/MaxRepositorySizeQuota.java
index f23d11f..90f3483 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/quota/MaxRepositorySizeQuota.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/quota/MaxRepositorySizeQuota.java
@@ -17,8 +17,10 @@
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Ordering;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ReceivePackInitializer;
 import com.google.gerrit.server.project.ProjectCache;
@@ -28,6 +30,9 @@
 import com.google.inject.name.Named;
 
 import org.apache.commons.lang.mutable.MutableLong;
+import org.eclipse.jgit.internal.storage.file.FileRepository;
+import org.eclipse.jgit.internal.storage.file.GC;
+import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.transport.PostReceiveHook;
 import org.eclipse.jgit.transport.ReceiveCommand;
@@ -144,15 +149,23 @@
   static class Loader extends CacheLoader<Project.NameKey, AtomicLong> {
 
     private final GitRepositoryManager gitManager;
+    private final boolean useGitObjectCount;
 
     @Inject
-    Loader(GitRepositoryManager gitManager) {
+    Loader(GitRepositoryManager gitManager,
+        PluginConfigFactory cfg,
+        @PluginName String pluginName) {
       this.gitManager = gitManager;
+      this.useGitObjectCount = cfg.getFromGerritConfig(pluginName)
+          .getBoolean("useGitObjectCount", false);
     }
 
     @Override
     public AtomicLong load(Project.NameKey project) throws IOException {
-      try (Repository git = gitManager.openRepository(project)){
+      try (Repository git = gitManager.openRepository(project)) {
+        if (useGitObjectCount) {
+          return new AtomicLong(getDiskUsageByGitObjectCount(git));
+        }
         return new AtomicLong(getDiskUsage(git.getDirectory()));
       }
     }
@@ -171,6 +184,12 @@
       });
       return size.longValue();
     }
+
+    private long getDiskUsageByGitObjectCount(Repository repo)
+        throws IOException {
+      RepoStatistics stats = new GC((FileRepository) repo).getStatistics();
+      return stats.sizeOfLooseObjects + stats.sizeOfPackedObjects;
+    }
   }
 
   @Override
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index ea254a7..31c32b9 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -119,6 +119,20 @@
     maxTotalSize = 20 m
 ```
 
+If one prefers computing a repository size by adding the size of the git objects,
+the following section should be added into the `gerrit.config` file:
+
+```
+  [plugin "quota"]
+        useGitObjectCount = true
+```
+
+<a id="useGitObjectCount">
+`plugin.quota.useGitObjectCount`
+: Use git object count. If true, *repoSize = looseObjectsSize +
+packedObjectsSize*, where *looseObjectsSize* and *packedObjectsSize* are given
+by JGit RepoStatistics. By default, false.
+
 Publication Schedule
 --------------------