From fabe060d3a435f116128851f828e35c2af5fde67 Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Mon, 10 Sep 2012 16:26:27 -0400 Subject: [PATCH] Strip folder name and .git from repo links in the project view --- src/com/gitblit/GitBlit.java | 646 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 files changed, 593 insertions(+), 53 deletions(-) diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index a57e605..c758654 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -30,18 +30,21 @@ import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TimeZone; +import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import javax.mail.Message; import javax.mail.MessagingException; @@ -55,6 +58,10 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache.FileKey; import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.storage.file.WindowCache; +import org.eclipse.jgit.storage.file.WindowCacheConfig; +import org.eclipse.jgit.transport.ServiceMayNotContinueException; import org.eclipse.jgit.transport.resolver.FileResolver; import org.eclipse.jgit.transport.resolver.RepositoryResolver; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; @@ -65,6 +72,7 @@ import org.slf4j.LoggerFactory; import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.AuthorizationControl; import com.gitblit.Constants.FederationRequest; import com.gitblit.Constants.FederationStrategy; import com.gitblit.Constants.FederationToken; @@ -72,7 +80,9 @@ import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; import com.gitblit.models.Metric; +import com.gitblit.models.ProjectModel; import com.gitblit.models.RepositoryModel; +import com.gitblit.models.SearchResult; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; import com.gitblit.models.SettingModel; @@ -80,6 +90,8 @@ import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.ByteFormat; +import com.gitblit.utils.ContainerUtils; +import com.gitblit.utils.DeepCopier; import com.gitblit.utils.FederationUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.JsonUtils; @@ -119,6 +131,12 @@ private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>(); private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>(); + + private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>(); + + private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>(); + + private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>(""); private RepositoryResolver<Void> repositoryResolver; @@ -136,7 +154,11 @@ private MailExecutor mailExecutor; + private LuceneExecutor luceneExecutor; + private TimeZone timezone; + + private FileBasedConfig projectConfigs; public GitBlit() { if (gitblit == null) { @@ -182,6 +204,16 @@ } return self().timezone; } + + /** + * Returns the user-defined blob encodings. + * + * @return an array of encodings, may be empty + */ + public static String [] getEncodings() { + return getStrings(Keys.web.blobEncodings).toArray(new String[0]); + } + /** * Returns the boolean value for the specified key. If the key does not @@ -248,6 +280,17 @@ */ public static List<String> getStrings(String key) { return self().settings.getStrings(key); + } + + /** + * Returns a map of space-separated key-value pairs from the specified key. + * + * @see IStoredSettings.getStrings(String key) + * @param name + * @return map of string, string + */ + public static Map<String, String> getMap(String key) { + return self().settings.getMap(key); } /** @@ -373,6 +416,38 @@ this.userService = userService; this.userService.setup(settings); } + + /** + * + * @return true if the user service supports credential changes + */ + public boolean supportsCredentialChanges() { + return userService.supportsCredentialChanges(); + } + + /** + * + * @return true if the user service supports display name changes + */ + public boolean supportsDisplayNameChanges() { + return userService.supportsDisplayNameChanges(); + } + + /** + * + * @return true if the user service supports email address changes + */ + public boolean supportsEmailAddressChanges() { + return userService.supportsEmailAddressChanges(); + } + + /** + * + * @return true if the user service supports team membership changes + */ + public boolean supportsTeamMembershipChanges() { + return userService.supportsTeamMembershipChanges(); + } /** * Authenticate a user based on a username and password. @@ -453,13 +528,31 @@ userCookie = new Cookie(Constants.NAME, ""); } else { // set cookie for login - char[] cookie = userService.getCookie(user); - userCookie = new Cookie(Constants.NAME, new String(cookie)); - userCookie.setMaxAge(Integer.MAX_VALUE); + String cookie = userService.getCookie(user); + if (StringUtils.isEmpty(cookie)) { + // create empty cookie + userCookie = new Cookie(Constants.NAME, ""); + } else { + // create real cookie + userCookie = new Cookie(Constants.NAME, cookie); + userCookie.setMaxAge(Integer.MAX_VALUE); + } } userCookie.setPath("/"); response.addCookie(userCookie); } + } + + /** + * Logout a user. + * + * @param user + */ + public void logout(UserModel user) { + if (userService == null) { + return; + } + userService.logout(user); } /** @@ -644,15 +737,80 @@ public boolean deleteTeam(String teamname) { return userService.deleteTeam(teamname); } + + /** + * Adds the repository to the list of cached repositories if Gitblit is + * configured to cache the repository list. + * + * @param name + */ + private void addToCachedRepositoryList(String name, RepositoryModel model) { + if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { + repositoryListCache.put(name, model); + } + } + + /** + * Removes the repository from the list of cached repositories. + * + * @param name + */ + private void removeFromCachedRepositoryList(String name) { + if (StringUtils.isEmpty(name)) { + return; + } + repositoryListCache.remove(name); + } /** - * Clears all the cached data for the specified repository. + * Clears all the cached metadata for the specified repository. * * @param repositoryName */ - public void clearRepositoryCache(String repositoryName) { + private void clearRepositoryMetadataCache(String repositoryName) { repositorySizeCache.remove(repositoryName); repositoryMetricsCache.remove(repositoryName); + } + + /** + * Resets the repository list cache. + * + */ + public void resetRepositoryListCache() { + logger.info("Repository cache manually reset"); + repositoryListCache.clear(); + } + + /** + * Calculate the checksum of settings that affect the repository list cache. + * @return a checksum + */ + private String getRepositoryListSettingsChecksum() { + StringBuilder ns = new StringBuilder(); + ns.append(settings.getString(Keys.git.cacheRepositoryList, "")).append('\n'); + ns.append(settings.getString(Keys.git.onlyAccessBareRepositories, "")).append('\n'); + ns.append(settings.getString(Keys.git.searchRepositoriesSubfolders, "")).append('\n'); + ns.append(settings.getString(Keys.git.searchRecursionDepth, "")).append('\n'); + ns.append(settings.getString(Keys.git.searchExclusions, "")).append('\n'); + String checksum = StringUtils.getSHA1(ns.toString()); + return checksum; + } + + /** + * Compare the last repository list setting checksum to the current checksum. + * If different then clear the cache so that it may be rebuilt. + * + * @return true if the cached repository list is valid since the last check + */ + private boolean isValidRepositoryList() { + String newChecksum = getRepositoryListSettingsChecksum(); + boolean valid = newChecksum.equals(repositoryListSettingsChecksum.get()); + repositoryListSettingsChecksum.set(newChecksum); + if (!valid && settings.getBoolean(Keys.git.cacheRepositoryList, true)) { + logger.info("Repository list settings have changed. Clearing repository list cache."); + repositoryListCache.clear(); + } + return valid; } /** @@ -662,9 +820,48 @@ * @return list of all repositories */ public List<String> getRepositoryList() { - return JGitUtils.getRepositoryList(repositoriesFolder, - settings.getBoolean(Keys.git.onlyAccessBareRepositories, false), - settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true)); + if (repositoryListCache.size() == 0 || !isValidRepositoryList()) { + // we are not caching OR we have not yet cached OR the cached list is invalid + long startTime = System.currentTimeMillis(); + List<String> repositories = JGitUtils.getRepositoryList(repositoriesFolder, + settings.getBoolean(Keys.git.onlyAccessBareRepositories, false), + settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true), + settings.getInteger(Keys.git.searchRecursionDepth, -1), + settings.getStrings(Keys.git.searchExclusions)); + + if (!settings.getBoolean(Keys.git.cacheRepositoryList, true)) { + // we are not caching + StringUtils.sortRepositorynames(repositories); + return repositories; + } else { + // we are caching this list + String msg = "{0} repositories identified in {1} msecs"; + + // optionally (re)calculate repository sizes + if (getBoolean(Keys.web.showRepositorySizes, true)) { + msg = "{0} repositories identified with calculated folder sizes in {1} msecs"; + for (String repository : repositories) { + RepositoryModel model = getRepositoryModel(repository); + if (!model.skipSizeCalculation) { + calculateSize(model); + } + } + } else { + // update cache + for (String repository : repositories) { + getRepositoryModel(repository); + } + } + + long duration = System.currentTimeMillis() - startTime; + logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration)); + } + } + + // return sorted copy of cached list + List<String> list = new ArrayList<String>(repositoryListCache.keySet()); + StringUtils.sortRepositorynames(list); + return list; } /** @@ -706,6 +903,12 @@ logger.error("GitBlit.getRepository(String) failed to find " + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e); } + } catch (ServiceMayNotContinueException e) { + r = null; + if (logError) { + logger.error("GitBlit.getRepository(String) failed to find " + + new File(repositoriesFolder, repositoryName).getAbsolutePath(), e); + } } return r; } @@ -717,6 +920,7 @@ * @return list of repository models accessible to user */ public List<RepositoryModel> getRepositoryModels(UserModel user) { + long methodStart = System.currentTimeMillis(); List<String> list = getRepositoryList(); List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(); for (String repo : list) { @@ -736,9 +940,15 @@ } } long duration = System.currentTimeMillis() - startTime; - logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs", + if (duration > 250) { + // only log calcualtion time if > 250 msecs + logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs", repoCount, duration)); + } } + long duration = System.currentTimeMillis() - methodStart; + logger.info(MessageFormat.format("{0} repository models loaded for {1} in {2} msecs", + repositories.size(), user == null ? "anonymous" : user.username, duration)); return repositories; } @@ -773,6 +983,203 @@ * @return repository model or null */ public RepositoryModel getRepositoryModel(String repositoryName) { + if (!repositoryListCache.containsKey(repositoryName)) { + RepositoryModel model = loadRepositoryModel(repositoryName); + if (model == null) { + return null; + } + addToCachedRepositoryList(repositoryName, model); + return model; + } + + // cached model + RepositoryModel model = repositoryListCache.get(repositoryName); + + // check for updates + Repository r = getRepository(repositoryName); + if (r == null) { + // repository is missing + removeFromCachedRepositoryList(repositoryName); + logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName)); + return null; + } + + FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r); + if (config.isOutdated()) { + // reload model + logger.info(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName)); + model = loadRepositoryModel(repositoryName); + removeFromCachedRepositoryList(repositoryName); + addToCachedRepositoryList(repositoryName, model); + } else { + // update a few repository parameters + if (!model.hasCommits) { + // update hasCommits, assume a repository only gains commits :) + model.hasCommits = JGitUtils.hasCommits(r); + } + + model.lastChange = JGitUtils.getLastChange(r); + } + r.close(); + + // return a copy of the cached model + return DeepCopier.copy(model); + } + + + /** + * Returns the map of project config. This map is cached and reloaded if + * the underlying projects.conf file changes. + * + * @return project config map + */ + private Map<String, ProjectModel> getProjectConfigs() { + if (projectConfigs.isOutdated()) { + + try { + projectConfigs.load(); + } catch (Exception e) { + } + + // project configs + String rootName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main"); + ProjectModel rootProject = new ProjectModel(rootName, true); + + Map<String, ProjectModel> configs = new HashMap<String, ProjectModel>(); + // cache the root project under its alias and an empty path + configs.put("", rootProject); + configs.put(rootProject.name.toLowerCase(), rootProject); + + for (String name : projectConfigs.getSubsections("project")) { + ProjectModel project; + if (name.equalsIgnoreCase(rootName)) { + project = rootProject; + } else { + project = new ProjectModel(name); + } + project.title = projectConfigs.getString("project", name, "title"); + project.description = projectConfigs.getString("project", name, "description"); + // TODO add more interesting metadata + // project manager? + // commit message regex? + // RW+ + // RW + // R + configs.put(name.toLowerCase(), project); + } + projectCache.clear(); + projectCache.putAll(configs); + } + return projectCache; + } + + /** + * Returns a list of project models for the user. + * + * @param user + * @return list of projects that are accessible to the user + */ + public List<ProjectModel> getProjectModels(UserModel user) { + Map<String, ProjectModel> configs = getProjectConfigs(); + + // per-user project lists, this accounts for security and visibility + Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>(); + // root project + map.put("", configs.get("")); + + for (RepositoryModel model : getRepositoryModels(user)) { + String rootPath = StringUtils.getRootPath(model.name).toLowerCase(); + if (!map.containsKey(rootPath)) { + ProjectModel project; + if (configs.containsKey(rootPath)) { + // clone the project model because it's repository list will + // be tailored for the requesting user + project = DeepCopier.copy(configs.get(rootPath)); + } else { + project = new ProjectModel(rootPath); + } + map.put(rootPath, project); + } + map.get(rootPath).addRepository(model); + } + + // sort projects, root project first + List<ProjectModel> projects = new ArrayList<ProjectModel>(map.values()); + Collections.sort(projects); + projects.remove(map.get("")); + projects.add(0, map.get("")); + return projects; + } + + /** + * Returns the project model for the specified user. + * + * @param name + * @param user + * @return a project model, or null if it does not exist + */ + public ProjectModel getProjectModel(String name, UserModel user) { + for (ProjectModel project : getProjectModels(user)) { + if (project.name.equalsIgnoreCase(name)) { + return project; + } + } + return null; + } + + /** + * Returns a project model for the Gitblit/system user. + * + * @param name a project name + * @return a project model or null if the project does not exist + */ + public ProjectModel getProjectModel(String name) { + Map<String, ProjectModel> configs = getProjectConfigs(); + ProjectModel project = configs.get(name.toLowerCase()); + if (project == null) { + return null; + } + // clone the object + project = DeepCopier.copy(project); + String folder = name.toLowerCase() + "/"; + for (String repository : getRepositoryList()) { + if (repository.toLowerCase().startsWith(folder)) { + project.addRepository(repository); + } + } + return project; + } + + /** + * 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 + * config. If the config changes are made within Gitblit this is fine as + * the returned config will still be flagged as dirty. BUT... if the config + * is manipulated outside Gitblit then it fails to recognize this as dirty. + * + * @param r + * @return a config + */ + private StoredConfig getRepositoryConfig(Repository r) { + try { + Field f = r.getClass().getDeclaredField("repoConfig"); + f.setAccessible(true); + StoredConfig config = (StoredConfig) f.get(r); + return config; + } catch (Exception e) { + logger.error("Failed to retrieve \"repoConfig\" via reflection", e); + } + return r.getConfig(); + } + + /** + * Create a repository model from the configuration and repository data. + * + * @param repositoryName + * @return a repositoryModel or null if the repository does not exist + */ + private RepositoryModel loadRepositoryModel(String repositoryName) { Repository r = getRepository(repositoryName); if (r == null) { return null; @@ -782,14 +1189,17 @@ model.hasCommits = JGitUtils.hasCommits(r); model.lastChange = JGitUtils.getLastChange(r); model.isBare = r.isBare(); - StoredConfig config = JGitUtils.readConfig(r); + + StoredConfig config = r.getConfig(); if (config != null) { model.description = getConfig(config, "description", ""); model.owner = getConfig(config, "owner", ""); model.useTickets = getConfig(config, "useTickets", false); model.useDocs = getConfig(config, "useDocs", false); model.accessRestriction = AccessRestrictionType.fromName(getConfig(config, - "accessRestriction", null)); + "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null))); + model.authorizationControl = AuthorizationControl.fromName(getConfig(config, + "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null))); model.showRemoteBranches = getConfig(config, "showRemoteBranches", false); model.isFrozen = getConfig(config, "isFrozen", false); model.showReadme = getConfig(config, "showReadme", false); @@ -798,20 +1208,48 @@ model.federationStrategy = FederationStrategy.fromName(getConfig(config, "federationStrategy", null)); model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList( - "gitblit", null, "federationSets"))); + Constants.CONFIG_GITBLIT, null, "federationSets"))); model.isFederated = getConfig(config, "isFederated", false); model.origin = config.getString("remote", "origin", "url"); model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList( - "gitblit", null, "preReceiveScript"))); + Constants.CONFIG_GITBLIT, null, "preReceiveScript"))); model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList( - "gitblit", null, "postReceiveScript"))); + Constants.CONFIG_GITBLIT, null, "postReceiveScript"))); model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList( - "gitblit", null, "mailingList"))); + Constants.CONFIG_GITBLIT, null, "mailingList"))); + model.indexedBranches = new ArrayList<String>(Arrays.asList(config.getStringList( + Constants.CONFIG_GITBLIT, null, "indexBranch"))); + + // Custom defined properties + model.customFields = new LinkedHashMap<String, String>(); + for (String aProperty : config.getNames(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS)) { + model.customFields.put(aProperty, config.getString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, aProperty)); + } } model.HEAD = JGitUtils.getHEADRef(r); model.availableRefs = JGitUtils.getAvailableHeadTargets(r); r.close(); return model; + } + + /** + * Determines if this server has the requested repository. + * + * @param name + * @return true if the repository exists + */ + public boolean hasRepository(String repositoryName) { + if (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); + } + Repository r = getRepository(repositoryName, false); + if (r == null) { + return false; + } + r.close(); + return true; } /** @@ -866,6 +1304,9 @@ repository.close(); } } + + // close any open index writer/searcher in the Lucene executor + luceneExecutor.close(repositoryName); } /** @@ -882,7 +1323,7 @@ if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) { return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name)); } - List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null); + List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null, getTimezone()); repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics); return new ArrayList<Metric>(metrics); } @@ -897,7 +1338,7 @@ * @return field value or defaultValue */ private String getConfig(StoredConfig config, String field, String defaultValue) { - String value = config.getString("gitblit", null, field); + String value = config.getString(Constants.CONFIG_GITBLIT, null, field); if (StringUtils.isEmpty(value)) { return defaultValue; } @@ -905,7 +1346,7 @@ } /** - * Returns the gitblit boolean vlaue for the specified key. If key is not + * Returns the gitblit boolean value for the specified key. If key is not * set, returns defaultValue. * * @param config @@ -914,7 +1355,7 @@ * @return field value or defaultValue */ private boolean getConfig(StoredConfig config, String field, boolean defaultValue) { - return config.getBoolean("gitblit", field, defaultValue); + return config.getBoolean(Constants.CONFIG_GITBLIT, field, defaultValue); } /** @@ -986,7 +1427,7 @@ } // clear the cache - clearRepositoryCache(repositoryName); + clearRepositoryMetadataCache(repositoryName); } // load repository @@ -999,6 +1440,8 @@ logger.error("Service not authorized", e); } catch (ServiceNotEnabledException e) { logger.error("Service not enabled", e); + } catch (ServiceMayNotContinueException e) { + logger.error("Service may not continue", e); } } @@ -1012,15 +1455,20 @@ repository.name, currentRef, repository.HEAD)); if (JGitUtils.setHEADtoRef(r, repository.HEAD)) { // clear the cache - clearRepositoryCache(repository.name); + clearRepositoryMetadataCache(repository.name); } } // close the repository object r.close(); } + + // update repository cache + removeFromCachedRepositoryList(repositoryName); + // model will actually be replaced on next load because config is stale + addToCachedRepositoryList(repository.name, repository); } - + /** * Updates the Gitblit configuration for the specified repository. * @@ -1030,35 +1478,60 @@ * the Gitblit repository model */ public void updateConfiguration(Repository r, RepositoryModel repository) { - StoredConfig config = JGitUtils.readConfig(r); - config.setString("gitblit", null, "description", repository.description); - config.setString("gitblit", null, "owner", repository.owner); - config.setBoolean("gitblit", null, "useTickets", repository.useTickets); - config.setBoolean("gitblit", null, "useDocs", repository.useDocs); - config.setString("gitblit", null, "accessRestriction", repository.accessRestriction.name()); - config.setBoolean("gitblit", null, "showRemoteBranches", repository.showRemoteBranches); - config.setBoolean("gitblit", null, "isFrozen", repository.isFrozen); - config.setBoolean("gitblit", null, "showReadme", repository.showReadme); - config.setBoolean("gitblit", null, "skipSizeCalculation", repository.skipSizeCalculation); - config.setBoolean("gitblit", null, "skipSummaryMetrics", repository.skipSummaryMetrics); - config.setStringList("gitblit", null, "federationSets", repository.federationSets); - config.setString("gitblit", null, "federationStrategy", + StoredConfig config = r.getConfig(); + config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description); + config.setString(Constants.CONFIG_GITBLIT, null, "owner", repository.owner); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs); + config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name()); + config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name()); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFrozen", repository.isFrozen); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "showReadme", repository.showReadme); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSizeCalculation", repository.skipSizeCalculation); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "skipSummaryMetrics", repository.skipSummaryMetrics); + config.setString(Constants.CONFIG_GITBLIT, null, "federationStrategy", repository.federationStrategy.name()); - config.setBoolean("gitblit", null, "isFederated", repository.isFederated); - if (!ArrayUtils.isEmpty(repository.preReceiveScripts)) { - config.setStringList("gitblit", null, "preReceiveScript", repository.preReceiveScripts); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated); + + updateList(config, "federationSets", repository.federationSets); + updateList(config, "preReceiveScript", repository.preReceiveScripts); + updateList(config, "postReceiveScript", repository.postReceiveScripts); + updateList(config, "mailingList", repository.mailingLists); + updateList(config, "indexBranch", repository.indexedBranches); + + // User Defined Properties + if (repository.customFields != null) { + if (repository.customFields.size() == 0) { + // clear section + config.unsetSection(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS); + } else { + for (Entry<String, String> property : repository.customFields.entrySet()) { + // set field + String key = property.getKey(); + String value = property.getValue(); + config.setString(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS, key, value); + } + } } - if (!ArrayUtils.isEmpty(repository.postReceiveScripts)) { - config.setStringList("gitblit", null, "postReceiveScript", - repository.postReceiveScripts); - } - if (!ArrayUtils.isEmpty(repository.mailingLists)) { - config.setStringList("gitblit", null, "mailingList", repository.mailingLists); - } + try { config.save(); } catch (IOException e) { logger.error("Failed to save repository config!", e); + } + } + + private void updateList(StoredConfig config, String field, List<String> list) { + // a null list is skipped, not cleared + // this is for RPC administration where an older manager might be used + if (list == null) { + return; + } + if (ArrayUtils.isEmpty(list)) { + config.unset(Constants.CONFIG_GITBLIT, null, field); + } else { + config.setStringList(Constants.CONFIG_GITBLIT, null, field, list); } } @@ -1083,16 +1556,18 @@ public boolean deleteRepository(String repositoryName) { try { closeRepository(repositoryName); + // clear the repository cache + clearRepositoryMetadataCache(repositoryName); + removeFromCachedRepositoryList(repositoryName); + File folder = new File(repositoriesFolder, repositoryName); if (folder.exists() && folder.isDirectory()) { FileUtils.delete(folder, FileUtils.RECURSIVE | FileUtils.RETRY); if (userService.deleteRepositoryRole(repositoryName)) { + logger.info(MessageFormat.format("Repository \"{0}\" deleted", repositoryName)); return true; } } - - // clear the repository cache - clearRepositoryCache(repositoryName); } catch (Throwable t) { logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t); } @@ -1643,6 +2118,20 @@ } return scripts; } + + /** + * Search the specified repositories using the Lucene query. + * + * @param query + * @param page + * @param pageSize + * @param repositories + * @return + */ + public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) { + List<SearchResult> srs = luceneExecutor.search(query, page, pageSize, repositories); + return srs; + } /** * Notify the administrators by email. @@ -1703,9 +2192,13 @@ // ensure that the current values are updated in the setting models for (String key : settings.getAllKeys(null)) { SettingModel setting = settingsModel.get(key); - if (setting != null) { - setting.currentValue = settings.getString(key, ""); + if (setting == null) { + // unreferenced setting, create a setting model + setting = new SettingModel(); + setting.name = key; + settingsModel.add(setting); } + setting.currentValue = settings.getString(key, ""); } settingsModel.pushScripts = getAllScripts(); return settingsModel; @@ -1720,6 +2213,10 @@ */ private ServerSettings loadSettingModels() { ServerSettings settingsModel = new ServerSettings(); + settingsModel.supportsCredentialChanges = userService.supportsCredentialChanges(); + settingsModel.supportsDisplayNameChanges = userService.supportsDisplayNameChanges(); + settingsModel.supportsEmailAddressChanges = userService.supportsEmailAddressChanges(); + settingsModel.supportsTeamMembershipChanges = userService.supportsTeamMembershipChanges(); try { // Read bundled Gitblit properties to extract setting descriptions. // This copy is pristine and only used for populating the setting @@ -1789,6 +2286,15 @@ repositoriesFolder = getRepositoriesFolder(); logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath()); repositoryResolver = new FileResolver<Void>(repositoriesFolder, true); + + // calculate repository list settings checksum for future config changes + repositoryListSettingsChecksum.set(getRepositoryListSettingsChecksum()); + + // build initial repository list + if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { + logger.info("Identifying available repositories..."); + getRepositoryList(); + } logTimezone("JVM", TimeZone.getDefault()); logTimezone(Constants.NAME, getTimezone()); @@ -1804,15 +2310,48 @@ loginService = new GitblitUserService(); } setUserService(loginService); + + // load and cache the project metadata + projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "projects.conf"), FS.detect()); + getProjectConfigs(); + mailExecutor = new MailExecutor(settings); if (mailExecutor.isReady()) { + logger.info("Mail executor is scheduled to process the message queue every 2 minutes."); scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES); } else { logger.warn("Mail server is not properly configured. Mail services disabled."); } + luceneExecutor = new LuceneExecutor(settings, repositoriesFolder); + logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes."); + scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES); if (startFederation) { configureFederation(); - } + } + + // Configure JGit + WindowCacheConfig cfg = new WindowCacheConfig(); + + cfg.setPackedGitWindowSize(settings.getFilesize(Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize())); + 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 { + WindowCache.reconfigure(cfg); + logger.debug(MessageFormat.format("{0} = {1,number,0}", Keys.git.packedGitWindowSize, cfg.getPackedGitWindowSize())); + 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); + } + + ContainerUtils.CVE_2007_0450.test(); } private void logTimezone(String type, TimeZone zone) { @@ -1831,7 +2370,6 @@ @Override public void contextInitialized(ServletContextEvent contextEvent) { servletContext = contextEvent.getServletContext(); - settingsModel = loadSettingModels(); if (settings == null) { // Gitblit WAR is running in a servlet container ServletContext context = contextEvent.getServletContext(); @@ -1870,7 +2408,8 @@ } } } - + + settingsModel = loadSettingModels(); serverStatus.servletContainer = servletContext.getServerInfo(); } @@ -1882,5 +2421,6 @@ public void contextDestroyed(ServletContextEvent contextEvent) { logger.info("Gitblit context destroyed by servlet container."); scheduledExecutor.shutdownNow(); + luceneExecutor.close(); } } -- Gitblit v1.9.1