James Moger
2014-06-09 ca4d98678c20e4033fdaca09ecbbf0f5952e0b84
src/main/java/com/gitblit/manager/RepositoryManager.java
@@ -63,12 +63,10 @@
import com.gitblit.Constants.FederationStrategy;
import com.gitblit.Constants.PermissionType;
import com.gitblit.Constants.RegistrantType;
import com.gitblit.GCExecutor;
import com.gitblit.GitBlitException;
import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.LuceneExecutor;
import com.gitblit.MirrorExecutor;
import com.gitblit.extensions.RepositoryLifeCycleListener;
import com.gitblit.models.ForkModel;
import com.gitblit.models.Metric;
import com.gitblit.models.RefModel;
@@ -77,6 +75,9 @@
import com.gitblit.models.SearchResult;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
import com.gitblit.service.GarbageCollectorService;
import com.gitblit.service.LuceneService;
import com.gitblit.service.MirrorService;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.ByteFormat;
import com.gitblit.utils.CommitCache;
@@ -114,22 +115,26 @@
   private final IRuntimeManager runtimeManager;
   private final IPluginManager pluginManager;
   private final IUserManager userManager;
   private final File repositoriesFolder;
   private LuceneExecutor luceneExecutor;
   private LuceneService luceneExecutor;
   private GCExecutor gcExecutor;
   private GarbageCollectorService gcExecutor;
   private MirrorExecutor mirrorExecutor;
   private MirrorService mirrorExecutor;
   public RepositoryManager(
         IRuntimeManager runtimeManager,
         IPluginManager pluginManager,
         IUserManager userManager) {
      this.settings = runtimeManager.getSettings();
      this.runtimeManager = runtimeManager;
      this.pluginManager = pluginManager;
      this.userManager = userManager;
      this.repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
   }
@@ -157,6 +162,8 @@
      configureJGit();
      configureCommitCache();
      confirmWriteAccess();
      return this;
   }
@@ -167,6 +174,7 @@
      gcExecutor.close();
      mirrorExecutor.close();
      closeAll();
      return this;
   }
@@ -418,8 +426,9 @@
         // update the fork origin repository with this repository clone
         if (!StringUtils.isEmpty(model.originRepository)) {
            if (repositoryListCache.containsKey(model.originRepository)) {
               RepositoryModel origin = repositoryListCache.get(model.originRepository);
            String originKey = model.originRepository.toLowerCase();
            if (repositoryListCache.containsKey(originKey)) {
               RepositoryModel origin = repositoryListCache.get(originKey);
               origin.addFork(model.name);
            }
         }
@@ -447,6 +456,21 @@
   private void clearRepositoryMetadataCache(String repositoryName) {
      repositorySizeCache.remove(repositoryName);
      repositoryMetricsCache.remove(repositoryName);
      CommitCache.instance().clear(repositoryName);
   }
   /**
    * Reset all caches for this repository.
    *
    * @param repositoryName
    * @since 1.5.1
    */
   @Override
   public void resetRepositoryCache(String repositoryName) {
      removeFromCachedRepositoryList(repositoryName);
      clearRepositoryMetadataCache(repositoryName);
      // force a reload of the repository data (ticket-82, issue-433)
      getRepositoryModel(repositoryName);
   }
   /**
@@ -457,6 +481,9 @@
   public void resetRepositoryListCache() {
      logger.info("Repository cache manually reset");
      repositoryListCache.clear();
      repositorySizeCache.clear();
      repositoryMetricsCache.clear();
      CommitCache.instance().clear();
   }
   /**
@@ -527,8 +554,9 @@
            // rebuild fork networks
            for (RepositoryModel model : repositoryListCache.values()) {
               if (!StringUtils.isEmpty(model.originRepository)) {
                  if (repositoryListCache.containsKey(model.originRepository)) {
                     RepositoryModel origin = repositoryListCache.get(model.originRepository);
                  String originKey = model.originRepository.toLowerCase();
                  if (repositoryListCache.containsKey(originKey)) {
                     RepositoryModel origin = repositoryListCache.get(originKey);
                     origin.addFork(model.name);
                  }
               }
@@ -661,7 +689,8 @@
      // http://stackoverflow.com/questions/17183110
      repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~");
      if (!repositoryListCache.containsKey(repositoryName)) {
      String repositoryKey = repositoryName.toLowerCase();
      if (!repositoryListCache.containsKey(repositoryKey)) {
         RepositoryModel model = loadRepositoryModel(repositoryName);
         if (model == null) {
            return null;
@@ -671,7 +700,7 @@
      }
      // cached model
      RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
      RepositoryModel model = repositoryListCache.get(repositoryKey);
      if (gcExecutor.isCollectingGarbage(model.name)) {
         // Gitblit is busy collecting garbage, use our cached model
@@ -778,10 +807,11 @@
      model.projectPath = StringUtils.getFirstPathElement(repositoryName);
      StoredConfig config = r.getConfig();
      boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
      boolean hasOrigin = false;
      if (config != null) {
         // Initialize description from description file
         hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
         if (getConfig(config,"description", null) == null) {
            File descFile = new File(r.getDirectory(), "description");
            if (descFile.exists()) {
@@ -794,6 +824,10 @@
         model.description = getConfig(config, "description", "");
         model.originRepository = getConfig(config, "originRepository", null);
         model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
         model.acceptNewPatchsets = getConfig(config, "acceptNewPatchsets", true);
         model.acceptNewTickets = getConfig(config, "acceptNewTickets", true);
         model.requireApproval = getConfig(config, "requireApproval", settings.getBoolean(Keys.tickets.requireApproval, false));
         model.mergeTo = getConfig(config, "mergeTo", null);
         model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false);
         model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null);
         model.allowForks = getConfig(config, "allowForks", true);
@@ -844,6 +878,9 @@
         }
      }
      model.HEAD = JGitUtils.getHEADRef(r);
      if (StringUtils.isEmpty(model.mergeTo)) {
         model.mergeTo = model.HEAD;
      }
      model.availableRefs = JGitUtils.getAvailableHeadTargets(r);
      model.sparkleshareId = JGitUtils.getSparkleshareId(r);
      model.hasCommits = JGitUtils.hasCommits(r);
@@ -928,26 +965,31 @@
    */
   @Override
   public String getFork(String username, String origin) {
      if (StringUtils.isEmpty(origin)) {
         return null;
      }
      String userProject = ModelUtils.getPersonalPath(username);
      if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
         String originKey = origin.toLowerCase();
         String userPath = userProject + "/";
         // collect all origin nodes in fork network
         Set<String> roots = new HashSet<String>();
         roots.add(origin);
         RepositoryModel originModel = repositoryListCache.get(origin);
         roots.add(originKey);
         RepositoryModel originModel = repositoryListCache.get(originKey);
         while (originModel != null) {
            if (!ArrayUtils.isEmpty(originModel.forks)) {
               for (String fork : originModel.forks) {
                  if (!fork.startsWith(userPath)) {
                     roots.add(fork);
                     roots.add(fork.toLowerCase());
                  }
               }
            }
            if (originModel.originRepository != null) {
               roots.add(originModel.originRepository);
               originModel = repositoryListCache.get(originModel.originRepository);
               String ooKey = originModel.originRepository.toLowerCase();
               roots.add(ooKey);
               originModel = repositoryListCache.get(ooKey);
            } else {
               // break
               originModel = null;
@@ -958,7 +1000,7 @@
            if (repository.startsWith(userPath)) {
               RepositoryModel model = repositoryListCache.get(repository);
               if (!StringUtils.isEmpty(model.originRepository)) {
                  if (roots.contains(model.originRepository)) {
                  if (roots.contains(model.originRepository.toLowerCase())) {
                     // user has a fork in this graph
                     return model.name;
                  }
@@ -975,7 +1017,7 @@
               settings.getStrings(Keys.git.searchExclusions));
         for (String repository : repositories) {
            RepositoryModel model = getRepositoryModel(userProject + "/" + repository);
            if (model.originRepository.equalsIgnoreCase(origin)) {
            if (model.originRepository != null && model.originRepository.equalsIgnoreCase(origin)) {
               // user has a fork
               return model.name;
            }
@@ -998,7 +1040,7 @@
         // find the root, cached
         RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
         while (model.originRepository != null) {
            model = repositoryListCache.get(model.originRepository);
            model = repositoryListCache.get(model.originRepository.toLowerCase());
         }
         ForkModel root = getForkModelFromCache(model.name);
         return root;
@@ -1078,12 +1120,49 @@
   }
   /**
    * Returns true if the repository is idle (not being accessed).
    *
    * @param repository
    * @return true if the repository is idle
    */
   @Override
   public boolean isIdle(Repository repository) {
      try {
         // Read the use count.
         // An idle use count is 2:
         // +1 for being in the cache
         // +1 for the repository parameter in this method
         Field useCnt = Repository.class.getDeclaredField("useCnt");
         useCnt.setAccessible(true);
         int useCount = ((AtomicInteger) useCnt.get(repository)).get();
         return useCount == 2;
      } catch (Exception e) {
         logger.warn(MessageFormat
               .format("Failed to reflectively determine use count for repository {0}",
                     repository.getDirectory().getPath()), e);
      }
      return false;
   }
   /**
    * Ensures that all cached repository are completely closed and their resources
    * are properly released.
    */
   @Override
   public void closeAll() {
      for (String repository : getRepositoryList()) {
         close(repository);
      }
   }
   /**
    * Ensure that a cached repository is completely closed and its resources
    * are properly released.
    *
    * @param repositoryName
    */
   private void closeRepository(String repositoryName) {
   @Override
   public void close(String repositoryName) {
      Repository repository = getRepository(repositoryName);
      if (repository == null) {
         return;
@@ -1108,7 +1187,7 @@
                     repositoryName), e);
      }
      if (uses > 0) {
         logger.info(MessageFormat
         logger.debug(MessageFormat
               .format("{0}.useCnt={1}, calling close() {2} time(s) to close object and ref databases",
                     repositoryName, uses, uses));
         for (int i = 0; i < uses; i++) {
@@ -1246,7 +1325,7 @@
                     "Failed to rename ''{0}'' because ''{1}'' already exists.",
                     repositoryName, repository.name));
            }
            closeRepository(repositoryName);
            close(repositoryName);
            File folder = new File(repositoriesFolder, repositoryName);
            File destFolder = new File(repositoriesFolder, repository.name);
            if (destFolder.exists()) {
@@ -1292,7 +1371,7 @@
            // update this repository's origin's fork list
            if (!StringUtils.isEmpty(repository.originRepository)) {
               RepositoryModel origin = repositoryListCache.get(repository.originRepository);
               RepositoryModel origin = repositoryListCache.get(repository.originRepository.toLowerCase());
               if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
                  origin.forks.remove(repositoryName);
                  origin.forks.add(repository.name);
@@ -1346,6 +1425,16 @@
      removeFromCachedRepositoryList(repositoryName);
      // model will actually be replaced on next load because config is stale
      addToCachedRepositoryList(repository);
      if (isCreate && pluginManager != null) {
         for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) {
            try {
               listener.onCreation(repository);
            } catch (Throwable t) {
               logger.error(String.format("failed to call plugin onCreation %s", repositoryName), t);
            }
         }
      }
   }
   /**
@@ -1362,6 +1451,18 @@
      config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
      config.setString(Constants.CONFIG_GITBLIT, null, "originRepository", repository.originRepository);
      config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
      config.setBoolean(Constants.CONFIG_GITBLIT, null, "acceptNewPatchsets", repository.acceptNewPatchsets);
      config.setBoolean(Constants.CONFIG_GITBLIT, null, "acceptNewTickets", repository.acceptNewTickets);
      if (settings.getBoolean(Keys.tickets.requireApproval, false) == repository.requireApproval) {
         // use default
         config.unset(Constants.CONFIG_GITBLIT, null, "requireApproval");
      } else {
         // override default
         config.setBoolean(Constants.CONFIG_GITBLIT, null, "requireApproval", repository.requireApproval);
      }
      if (!StringUtils.isEmpty(repository.mergeTo)) {
         config.setString(Constants.CONFIG_GITBLIT, null, "mergeTo", repository.mergeTo);
      }
      config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags);
      if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) ||
            repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) {
@@ -1450,6 +1551,17 @@
   }
   /**
    * Returns true if the repository can be deleted.
    *
    * @return true if the repository can be deleted
    */
   @Override
   public boolean canDelete(RepositoryModel repository) {
      return settings.getBoolean(Keys.web.allowDeletingNonEmptyRepositories, true)
               || !repository.hasCommits;
   }
   /**
    * Deletes the repository from the file system and removes the repository
    * permission from all repository users.
    *
@@ -1470,8 +1582,14 @@
    */
   @Override
   public boolean deleteRepository(String repositoryName) {
      RepositoryModel repository = getRepositoryModel(repositoryName);
      if (!canDelete(repository)) {
         logger.warn("Attempt to delete {} rejected!", repositoryName);
         return false;
      }
      try {
         closeRepository(repositoryName);
         close(repositoryName);
         // clear the repository cache
         clearRepositoryMetadataCache(repositoryName);
@@ -1485,6 +1603,16 @@
            FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY);
            if (userManager.deleteRepositoryRole(repositoryName)) {
               logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName));
               if (pluginManager != null) {
                  for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) {
                     try {
                        listener.onDeletion(repository);
                     } catch (Throwable t) {
                        logger.error(String.format("failed to call plugin onDeletion %s", repositoryName), t);
                     }
                  }
               }
               return true;
            }
         }
@@ -1644,7 +1772,7 @@
   }
   protected void configureLuceneIndexing() {
      luceneExecutor = new LuceneExecutor(settings, this);
      luceneExecutor = new LuceneService(settings, this);
      int period = 2;
      scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, period,  TimeUnit.MINUTES);
      logger.info("Lucene will process indexed branches every {} minutes.", period);
@@ -1652,7 +1780,7 @@
   protected void configureGarbageCollector() {
      // schedule gc engine
      gcExecutor = new GCExecutor(settings, this);
      gcExecutor = new GarbageCollectorService(settings, this);
      if (gcExecutor.isReady()) {
         logger.info("Garbage Collector (GC) will scan repositories every 24 hours.");
         Calendar c = Calendar.getInstance();
@@ -1680,7 +1808,7 @@
   }
   protected void configureMirrorExecutor() {
      mirrorExecutor = new MirrorExecutor(settings, this);
      mirrorExecutor = new MirrorService(settings, this);
      if (mirrorExecutor.isReady()) {
         int mins = TimeUtils.convertFrequencyToMinutes(settings.getString(Keys.git.mirrorPeriod, "30 mins"));
         if (mins < 5) {
@@ -1754,4 +1882,23 @@
               daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
      }
   }
   protected void confirmWriteAccess() {
      if (runtimeManager.isServingRepositories()) {
         try {
            if (!getRepositoriesFolder().exists()) {
               getRepositoriesFolder().mkdirs();
            }
            File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
            file.delete();
         } catch (Exception e) {
            logger.error("");
            logger.error(Constants.BORDER2);
            logger.error("Please check filesystem permissions!");
            logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
            logger.error(Constants.BORDER2);
            logger.error("");
         }
      }
   }
}