From eecaad8b8e2c447429c31a01d49260ddd6b4ee03 Mon Sep 17 00:00:00 2001 From: Paul Martin <paul@paulsputer.com> Date: Sat, 16 Apr 2016 17:35:32 -0400 Subject: [PATCH] Proof of concept #1026 --- src/main/java/com/gitblit/manager/RepositoryManager.java | 357 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 files changed, 304 insertions(+), 53 deletions(-) diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java index 0a3f663..8d1a6a7 100644 --- a/src/main/java/com/gitblit/manager/RepositoryManager.java +++ b/src/main/java/com/gitblit/manager/RepositoryManager.java @@ -21,6 +21,7 @@ import java.lang.reflect.Field; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.Charset; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -52,6 +53,7 @@ import org.eclipse.jgit.storage.file.WindowCacheConfig; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.eclipse.jgit.util.RawParseUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,6 +68,7 @@ import com.gitblit.GitBlitException; import com.gitblit.IStoredSettings; import com.gitblit.Keys; +import com.gitblit.extensions.RepositoryLifeCycleListener; import com.gitblit.models.ForkModel; import com.gitblit.models.Metric; import com.gitblit.models.RefModel; @@ -88,6 +91,8 @@ import com.gitblit.utils.ObjectCache; import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; +import com.google.inject.Inject; +import com.google.inject.Singleton; /** * Repository manager creates, updates, deletes and caches git repositories. It @@ -96,6 +101,7 @@ * @author James Moger * */ +@Singleton public class RepositoryManager implements IRepositoryManager { private final Logger logger = LoggerFactory.getLogger(getClass()); @@ -114,9 +120,13 @@ private final IRuntimeManager runtimeManager; - private final IUserManager userManager; + private final IPluginManager pluginManager; - private final File repositoriesFolder; + private final IUserManager userManager; + + private final IFilestoreManager filestoreManager; + + private File repositoriesFolder; private LuceneService luceneExecutor; @@ -124,18 +134,23 @@ private MirrorService mirrorExecutor; + @Inject public RepositoryManager( IRuntimeManager runtimeManager, - IUserManager userManager) { + IPluginManager pluginManager, + IUserManager userManager, + IFilestoreManager filestoreManager) { this.settings = runtimeManager.getSettings(); this.runtimeManager = runtimeManager; + this.pluginManager = pluginManager; this.userManager = userManager; - this.repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git"); + this.filestoreManager = filestoreManager; } @Override public RepositoryManager start() { + repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git"); logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath()); // initialize utilities @@ -157,6 +172,8 @@ configureJGit(); configureCommitCache(); + confirmWriteAccess(); + return this; } @@ -167,6 +184,7 @@ gcExecutor.close(); mirrorExecutor.close(); + closeAll(); return this; } @@ -414,12 +432,14 @@ @Override public void addToCachedRepositoryList(RepositoryModel model) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { - repositoryListCache.put(model.name.toLowerCase(), model); + String key = getRepositoryKey(model.name); + repositoryListCache.put(key, model); // 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 = getRepositoryKey(model.originRepository); + if (repositoryListCache.containsKey(originKey)) { + RepositoryModel origin = repositoryListCache.get(originKey); origin.addFork(model.name); } } @@ -436,7 +456,8 @@ if (StringUtils.isEmpty(name)) { return null; } - return repositoryListCache.remove(name.toLowerCase()); + String key = getRepositoryKey(name); + return repositoryListCache.remove(key); } /** @@ -447,6 +468,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); } /** @@ -459,6 +495,7 @@ repositoryListCache.clear(); repositorySizeCache.clear(); repositoryMetricsCache.clear(); + CommitCache.instance().clear(); } /** @@ -529,8 +566,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 = getRepositoryKey(model.originRepository); + if (repositoryListCache.containsKey(originKey)) { + RepositoryModel origin = repositoryListCache.get(originKey); origin.addFork(model.name); } } @@ -564,15 +602,13 @@ /** * Returns the JGit repository for the specified name. * - * @param repositoryName + * @param name * @param logError * @return repository or null */ @Override - public Repository getRepository(String repositoryName, boolean logError) { - // Decode url-encoded repository name (issue-278) - // http://stackoverflow.com/questions/17183110 - repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~"); + public Repository getRepository(String name, boolean logError) { + String repositoryName = fixRepositoryName(name); if (isCollectingGarbage(repositoryName)) { logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName)); @@ -594,6 +630,27 @@ } } return r; + } + + /** + * Returns the list of all repository models. + * + * @return list of all repository models + */ + @Override + public List<RepositoryModel> getRepositoryModels() { + long methodStart = System.currentTimeMillis(); + List<String> list = getRepositoryList(); + List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(); + for (String repo : list) { + RepositoryModel model = getRepositoryModel(repo); + if (model != null) { + repositories.add(model); + } + } + long duration = System.currentTimeMillis() - methodStart; + logger.info(MessageFormat.format("{0} repository models loaded in {1} msecs", duration)); + return repositories; } /** @@ -654,16 +711,15 @@ * Returns the repository model for the specified repository. This method * does not consider user access permissions. * - * @param repositoryName + * @param name * @return repository model or null */ @Override - public RepositoryModel getRepositoryModel(String repositoryName) { - // Decode url-encoded repository name (issue-278) - // http://stackoverflow.com/questions/17183110 - repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~"); + public RepositoryModel getRepositoryModel(String name) { + String repositoryName = fixRepositoryName(name); - if (!repositoryListCache.containsKey(repositoryName)) { + String repositoryKey = getRepositoryKey(repositoryName); + if (!repositoryListCache.containsKey(repositoryKey)) { RepositoryModel model = loadRepositoryModel(repositoryName); if (model == null) { return null; @@ -673,9 +729,9 @@ } // cached model - RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase()); + RepositoryModel model = repositoryListCache.get(repositoryKey); - if (gcExecutor.isCollectingGarbage(model.name)) { + if (isCollectingGarbage(model.name)) { // Gitblit is busy collecting garbage, use our cached model RepositoryModel rm = DeepCopier.copy(model); rm.isCollectingGarbage = true; @@ -731,6 +787,52 @@ } /** + * Replaces illegal character patterns in a repository name. + * + * @param repositoryName + * @return a corrected name + */ + private String fixRepositoryName(String repositoryName) { + if (StringUtils.isEmpty(repositoryName)) { + return repositoryName; + } + + // Decode url-encoded repository name (issue-278) + // http://stackoverflow.com/questions/17183110 + String name = repositoryName.replace("%7E", "~").replace("%7e", "~"); + name = name.replace("%2F", "/").replace("%2f", "/"); + + if (name.charAt(name.length() - 1) == '/') { + name = name.substring(0, name.length() - 1); + } + + // strip duplicate-slashes from requests for repositoryName (ticket-117, issue-454) + // specify first char as slash so we strip leading slashes + char lastChar = '/'; + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) { + if (c == '/' && lastChar == c) { + continue; + } + sb.append(c); + lastChar = c; + } + + return sb.toString(); + } + + /** + * Returns the cache key for the repository name. + * + * @param repositoryName + * @return the cache key for the repository + */ + private String getRepositoryKey(String repositoryName) { + String name = fixRepositoryName(repositoryName); + return StringUtils.stripDotGit(name).toLowerCase(); + } + + /** * Workaround JGit. I need to access the raw config object directly in order * to see if the config is dirty so that I can reload a repository model. * If I use the stock JGit method to get the config it already reloads the @@ -780,10 +882,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()) { @@ -796,6 +899,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); @@ -846,6 +953,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); @@ -897,7 +1007,8 @@ if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // if we are caching use the cache to determine availability // otherwise we end up adding a phantom repository to the cache - return repositoryListCache.containsKey(repositoryName.toLowerCase()); + String key = getRepositoryKey(repositoryName); + return repositoryListCache.containsKey(key); } Repository r = getRepository(repositoryName, false); if (r == null) { @@ -930,26 +1041,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 = getRepositoryKey(origin); 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 = getRepositoryKey(originModel.originRepository); + roots.add(ooKey); + originModel = repositoryListCache.get(ooKey); } else { // break originModel = null; @@ -960,7 +1076,8 @@ if (repository.startsWith(userPath)) { RepositoryModel model = repositoryListCache.get(repository); if (!StringUtils.isEmpty(model.originRepository)) { - if (roots.contains(model.originRepository)) { + String ooKey = getRepositoryKey(model.originRepository); + if (roots.contains(ooKey)) { // user has a fork in this graph return model.name; } @@ -977,7 +1094,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,9 +1115,18 @@ public ForkModel getForkNetwork(String repository) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // find the root, cached - RepositoryModel model = repositoryListCache.get(repository.toLowerCase()); + String key = getRepositoryKey(repository); + RepositoryModel model = repositoryListCache.get(key); + if (model == null) { + return null; + } + while (model.originRepository != null) { - model = repositoryListCache.get(model.originRepository); + String originKey = getRepositoryKey(model.originRepository); + model = repositoryListCache.get(originKey); + if (model == null) { + return null; + } } ForkModel root = getForkModelFromCache(model.name); return root; @@ -1016,7 +1142,8 @@ } private ForkModel getForkModelFromCache(String repository) { - RepositoryModel model = repositoryListCache.get(repository.toLowerCase()); + String key = getRepositoryKey(repository); + RepositoryModel model = repositoryListCache.get(key); if (model == null) { return null; } @@ -1080,12 +1207,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; @@ -1110,7 +1274,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++) { @@ -1194,7 +1358,7 @@ } /** - * Creates/updates the repository model keyed by reopsitoryName. Saves all + * Creates/updates the repository model keyed by repositoryName. Saves all * repository settings in .git/config. This method allows for renaming * repositories and will update user access permissions accordingly. * @@ -1210,7 +1374,7 @@ @Override public void updateRepositoryModel(String repositoryName, RepositoryModel repository, boolean isCreate) throws GitBlitException { - if (gcExecutor.isCollectingGarbage(repositoryName)) { + if (isCollectingGarbage(repositoryName)) { throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}", repositoryName)); } @@ -1222,6 +1386,7 @@ repository.name = repository.name.substring(projectPath.length() + 1); } } + boolean isRename = false; if (isCreate) { // ensure created repository name ends with .git if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { @@ -1238,7 +1403,8 @@ r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared); } else { // rename repository - if (!repositoryName.equalsIgnoreCase(repository.name)) { + isRename = !repositoryName.equalsIgnoreCase(repository.name); + if (isRename) { if (!repository.name.toLowerCase().endsWith( org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) { repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT; @@ -1248,7 +1414,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()) { @@ -1294,7 +1460,8 @@ // update this repository's origin's fork list if (!StringUtils.isEmpty(repository.originRepository)) { - RepositoryModel origin = repositoryListCache.get(repository.originRepository); + String originKey = getRepositoryKey(repository.originRepository); + RepositoryModel origin = repositoryListCache.get(originKey); if (origin != null && !ArrayUtils.isEmpty(origin.forks)) { origin.forks.remove(repositoryName); origin.forks.add(repository.name); @@ -1348,6 +1515,24 @@ 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); + } + } + } else if (isRename && pluginManager != null) { + for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) { + try { + listener.onRename(repositoryName, repository); + } catch (Throwable t) { + logger.error(String.format("failed to call plugin onRename %s", repositoryName), t); + } + } + } } /** @@ -1364,6 +1549,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"))) { @@ -1452,6 +1649,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. * @@ -1472,8 +1680,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); @@ -1487,6 +1701,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; } } @@ -1646,10 +1870,11 @@ } protected void configureLuceneIndexing() { - 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); + luceneExecutor = new LuceneService(settings, this, filestoreManager); + String frequency = settings.getString(Keys.web.luceneFrequency, "2 mins"); + int mins = TimeUtils.convertFrequencyToMinutes(frequency, 2); + scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, mins, TimeUnit.MINUTES); + logger.info("Lucene will process indexed branches every {} minutes.", mins); } protected void configureGarbageCollector() { @@ -1684,10 +1909,7 @@ protected void configureMirrorExecutor() { mirrorExecutor = new MirrorService(settings, this); if (mirrorExecutor.isReady()) { - int mins = TimeUtils.convertFrequencyToMinutes(settings.getString(Keys.git.mirrorPeriod, "30 mins")); - if (mins < 5) { - mins = 5; - } + int mins = TimeUtils.convertFrequencyToMinutes(settings.getString(Keys.git.mirrorPeriod, "30 mins"), 5); int delay = 1; scheduledExecutor.scheduleAtFixedRate(mirrorExecutor, delay, mins, TimeUnit.MINUTES); logger.info("Mirror service will fetch updates every {} minutes.", mins); @@ -1705,7 +1927,6 @@ cfg.setPackedGitLimit(settings.getFilesize(Keys.git.packedGitLimit, cfg.getPackedGitLimit())); cfg.setDeltaBaseCacheLimit(settings.getFilesize(Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit())); cfg.setPackedGitOpenFiles(settings.getFilesize(Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles())); - cfg.setStreamFileThreshold(settings.getFilesize(Keys.git.streamFileThreshold, cfg.getStreamFileThreshold())); cfg.setPackedGitMMAP(settings.getBoolean(Keys.git.packedGitMmap, cfg.isPackedGitMMAP())); try { @@ -1714,10 +1935,23 @@ logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitLimit, cfg.getPackedGitLimit())); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.deltaBaseCacheLimit, cfg.getDeltaBaseCacheLimit())); logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitOpenFiles, cfg.getPackedGitOpenFiles())); - logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.streamFileThreshold, cfg.getStreamFileThreshold())); logger.debug(MessageFormat.format("{0} = {1}", Keys.git.packedGitMmap, cfg.isPackedGitMMAP())); } catch (IllegalArgumentException e) { logger.error("Failed to configure JGit parameters!", e); + } + + try { + // issue-486/ticket-151: UTF-9 & UTF-18 + // issue-560/ticket-237: 'UTF8' + Field field = RawParseUtils.class.getDeclaredField("encodingAliases"); + field.setAccessible(true); + Map<String, Charset> encodingAliases = (Map<String, Charset>) field.get(null); + encodingAliases.put("'utf8'", RawParseUtils.UTF8_CHARSET); + encodingAliases.put("utf-9", RawParseUtils.UTF8_CHARSET); + encodingAliases.put("utf-18", RawParseUtils.UTF8_CHARSET); + logger.info("Alias 'UTF8', UTF-9 & UTF-18 encodings as UTF-8 in JGit"); + } catch (Throwable t) { + logger.error("Failed to inject UTF-9 & UTF-18 encoding aliases into JGit", t); } } @@ -1756,4 +1990,21 @@ daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start))); } } + + protected void confirmWriteAccess() { + 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(""); + } + } } -- Gitblit v1.9.1