| | |
| | | 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.models.ForkModel; |
| | | import com.gitblit.models.Metric; |
| | | import com.gitblit.models.RefModel; |
| | |
| | | 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; |
| | |
| | | |
| | | 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, |
| | |
| | | } |
| | | |
| | | @Override |
| | | public IManager setup() { |
| | | logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath()); |
| | | public RepositoryManager start() { |
| | | logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath()); |
| | | |
| | | // initialize utilities |
| | | String prefix = settings.getString(Keys.git.userRepositoryPrefix, "~"); |
| | |
| | | |
| | | // build initial repository list |
| | | if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { |
| | | logger.info("Identifying available repositories..."); |
| | | logger.info("Identifying repositories..."); |
| | | getRepositoryList(); |
| | | } |
| | | |
| | |
| | | configureJGit(); |
| | | configureCommitCache(); |
| | | |
| | | confirmWriteAccess(); |
| | | |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public IManager stop() { |
| | | public RepositoryManager stop() { |
| | | scheduledExecutor.shutdownNow(); |
| | | luceneExecutor.close(); |
| | | gcExecutor.close(); |
| | | mirrorExecutor.close(); |
| | | |
| | | closeAll(); |
| | | return this; |
| | | } |
| | | |
| | |
| | | |
| | | // 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); |
| | | } |
| | | } |
| | |
| | | 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); |
| | | } |
| | | |
| | | /** |
| | |
| | | public void resetRepositoryListCache() { |
| | | logger.info("Repository cache manually reset"); |
| | | repositoryListCache.clear(); |
| | | repositorySizeCache.clear(); |
| | | repositoryMetricsCache.clear(); |
| | | CommitCache.instance().clear(); |
| | | } |
| | | |
| | | /** |
| | |
| | | // 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); |
| | | } |
| | | } |
| | |
| | | // 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; |
| | |
| | | } |
| | | |
| | | // 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 |
| | |
| | | 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()) { |
| | |
| | | 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); |
| | |
| | | } |
| | | } |
| | | 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); |
| | |
| | | */ |
| | | @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; |
| | |
| | | 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; |
| | | } |
| | |
| | | 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; |
| | | } |
| | |
| | | // 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; |
| | |
| | | } |
| | | |
| | | /** |
| | | * 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; |
| | |
| | | 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++) { |
| | |
| | | "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()) { |
| | |
| | | |
| | | // 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); |
| | |
| | | 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"))) { |
| | |
| | | @Override |
| | | public boolean deleteRepository(String repositoryName) { |
| | | try { |
| | | closeRepository(repositoryName); |
| | | close(repositoryName); |
| | | // clear the repository cache |
| | | clearRepositoryMetadataCache(repositoryName); |
| | | |
| | |
| | | } |
| | | |
| | | protected void configureLuceneIndexing() { |
| | | luceneExecutor = new LuceneExecutor(settings, this); |
| | | scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES); |
| | | logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes."); |
| | | 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); |
| | | } |
| | | |
| | | protected void configureGarbageCollector() { |
| | | // schedule gc engine |
| | | gcExecutor = new GCExecutor(settings, this); |
| | | gcExecutor = new GarbageCollectorService(settings, this); |
| | | if (gcExecutor.isReady()) { |
| | | logger.info("GC executor is scheduled to scan repositories every 24 hours."); |
| | | logger.info("Garbage Collector (GC) will scan repositories every 24 hours."); |
| | | Calendar c = Calendar.getInstance(); |
| | | c.set(Calendar.HOUR_OF_DAY, settings.getInteger(Keys.git.garbageCollectionHour, 0)); |
| | | c.set(Calendar.MINUTE, 0); |
| | |
| | | } |
| | | logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when)); |
| | | scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60 * 24, TimeUnit.MINUTES); |
| | | } else { |
| | | logger.info("Garbage Collector (GC) is disabled."); |
| | | } |
| | | } |
| | | |
| | | 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) { |
| | |
| | | } |
| | | int delay = 1; |
| | | scheduledExecutor.scheduleAtFixedRate(mirrorExecutor, delay, mins, TimeUnit.MINUTES); |
| | | logger.info("Mirror executor is scheduled to fetch updates every {} minutes.", mins); |
| | | logger.info("Mirror service will fetch updates every {} minutes.", mins); |
| | | logger.info("Next scheduled mirror fetch is in {} minutes", delay); |
| | | } else { |
| | | logger.info("Mirror service is disabled."); |
| | | } |
| | | } |
| | | |
| | |
| | | protected void configureCommitCache() { |
| | | int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14); |
| | | if (daysToCache <= 0) { |
| | | logger.info("commit cache disabled"); |
| | | logger.info("Commit cache is disabled"); |
| | | } else { |
| | | long start = System.nanoTime(); |
| | | long repoCount = 0; |
| | | long commitCount = 0; |
| | | logger.info(MessageFormat.format("preparing {0} day commit cache. please wait...", daysToCache)); |
| | | logger.info(MessageFormat.format("Preparing {0} day commit cache. please wait...", daysToCache)); |
| | | CommitCache.instance().setCacheDays(daysToCache); |
| | | Date cutoff = CommitCache.instance().getCutoffDate(); |
| | | for (String repositoryName : getRepositoryList()) { |
| | |
| | | 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(""); |
| | | } |
| | | } |
| | | } |
| | | } |