From 4c835e61e8ea2d5af2acf0c85c3c1f0d06f419df Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Wed, 26 Oct 2011 17:19:55 -0400
Subject: [PATCH] Documentation.
---
src/com/gitblit/GitBlit.java | 446 ++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 355 insertions(+), 91 deletions(-)
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 7c49969..19edf20 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -15,12 +15,16 @@
*/
package com.gitblit;
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -34,6 +38,7 @@
import javax.mail.Message;
import javax.mail.MessagingException;
+import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.http.Cookie;
@@ -58,12 +63,20 @@
import com.gitblit.Constants.FederationToken;
import com.gitblit.models.FederationModel;
import com.gitblit.models.FederationProposal;
+import com.gitblit.models.FederationSet;
+import com.gitblit.models.Metric;
+import com.gitblit.models.ObjectCache;
import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.ServerStatus;
+import com.gitblit.models.SettingModel;
import com.gitblit.models.UserModel;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.FederationUtils;
import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.MetricUtils;
import com.gitblit.utils.StringUtils;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
/**
* GitBlit is the servlet context listener singleton that acts as the core for
@@ -94,7 +107,13 @@
private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
+ private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>();
+
+ private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
+
private RepositoryResolver<Void> repositoryResolver;
+
+ private ServletContext servletContext;
private File repositoriesFolder;
@@ -103,6 +122,10 @@
private IUserService userService;
private IStoredSettings settings;
+
+ private ServerSettings settingsModel;
+
+ private ServerStatus serverStatus;
private MailExecutor mailExecutor;
@@ -224,6 +247,23 @@
}
/**
+ * Updates the list of server settings.
+ *
+ * @param settings
+ * @return true if the update succeeded
+ */
+ public boolean updateSettings(Map<String, String> updatedSettings) {
+ return settings.saveSettings(updatedSettings);
+ }
+
+ public ServerStatus getStatus() {
+ // update heap memory status
+ serverStatus.heapAllocated = Runtime.getRuntime().totalMemory();
+ serverStatus.heapFree = Runtime.getRuntime().freeMemory();
+ return serverStatus;
+ }
+
+ /**
* Returns the list of non-Gitblit clone urls. This allows Gitblit to
* advertise alternative urls for Git client repository access.
*
@@ -247,6 +287,7 @@
public void setUserService(IUserService userService) {
logger.info("Setting up user service " + userService.toString());
this.userService = userService;
+ this.userService.setup(settings);
}
/**
@@ -409,9 +450,26 @@
*/
public void updateUserModel(String username, UserModel user, boolean isCreate)
throws GitBlitException {
+ if (!username.equalsIgnoreCase(user.username)) {
+ if (userService.getUserModel(user.username) != null) {
+ throw new GitBlitException(MessageFormat.format(
+ "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
+ user.username));
+ }
+ }
if (!userService.updateUserModel(username, user)) {
throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
}
+ }
+
+ /**
+ * Clears all the cached data for the specified repository.
+ *
+ * @param repositoryName
+ */
+ public void clearRepositoryCache(String repositoryName) {
+ repositorySizeCache.remove(repositoryName);
+ repositoryMetricsCache.remove(repositoryName);
}
/**
@@ -466,6 +524,20 @@
repositories.add(model);
}
}
+ if (getBoolean(Keys.web.showRepositorySizes, true)) {
+ int repoCount = 0;
+ long startTime = System.currentTimeMillis();
+ ByteFormat byteFormat = new ByteFormat();
+ for (RepositoryModel model : repositories) {
+ if (!model.skipSizeCalculation) {
+ repoCount++;
+ model.size = byteFormat.format(calculateSize(model));
+ }
+ }
+ long duration = System.currentTimeMillis() - startTime;
+ logger.info(MessageFormat.format("{0} repository sizes calculated in {1} msecs",
+ repoCount, duration));
+ }
return repositories;
}
@@ -519,8 +591,12 @@
model.showRemoteBranches = getConfig(config, "showRemoteBranches", false);
model.isFrozen = getConfig(config, "isFrozen", false);
model.showReadme = getConfig(config, "showReadme", false);
+ model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false);
+ model.skipSummaryMetrics = getConfig(config, "skipSummaryMetrics", false);
model.federationStrategy = FederationStrategy.fromName(getConfig(config,
"federationStrategy", null));
+ model.federationSets = new ArrayList<String>(Arrays.asList(config.getStringList(
+ "gitblit", null, "federationSets")));
model.isFederated = getConfig(config, "isFederated", false);
model.origin = config.getString("remote", "origin", "url");
}
@@ -529,14 +605,22 @@
}
/**
- * Returns the size in bytes of the repository.
+ * Returns the size in bytes of the repository. Gitblit caches the
+ * repository sizes to reduce the performance penalty of recursive
+ * calculation. The cache is updated if the repository has been changed
+ * since the last calculation.
*
* @param model
* @return size in bytes
*/
public long calculateSize(RepositoryModel model) {
+ if (repositorySizeCache.hasCurrent(model.name, model.lastChange)) {
+ return repositorySizeCache.getObject(model.name);
+ }
File gitDir = FileKey.resolve(new File(repositoriesFolder, model.name), FS.DETECTED);
- return com.gitblit.utils.FileUtils.folderSize(gitDir);
+ long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
+ repositorySizeCache.updateObject(model.name, model.lastChange, size);
+ return size;
}
/**
@@ -575,7 +659,26 @@
}
/**
- * Returns the gitblit string vlaue for the specified key. If key is not
+ * Returns the metrics for the default branch of the specified repository.
+ * This method builds a metrics cache. The cache is updated if the
+ * repository is updated. A new copy of the metrics list is returned on each
+ * call so that modifications to the list are non-destructive.
+ *
+ * @param model
+ * @param repository
+ * @return a new array list of metrics
+ */
+ public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
+ if (repositoryMetricsCache.hasCurrent(model.name, model.lastChange)) {
+ return new ArrayList<Metric>(repositoryMetricsCache.getObject(model.name));
+ }
+ List<Metric> metrics = MetricUtils.getDateMetrics(repository, null, true, null);
+ repositoryMetricsCache.updateObject(model.name, model.lastChange, metrics);
+ return new ArrayList<Metric>(metrics);
+ }
+
+ /**
+ * Returns the gitblit string value for the specified key. If key is not
* set, returns defaultValue.
*
* @param config
@@ -637,6 +740,15 @@
} else {
// rename repository
if (!repositoryName.equalsIgnoreCase(repository.name)) {
+ if (!repository.name.toLowerCase().endsWith(
+ org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
+ repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
+ }
+ if (new File(repositoriesFolder, repository.name).exists()) {
+ throw new GitBlitException(MessageFormat.format(
+ "Failed to rename ''{0}'' because ''{1}'' already exists.",
+ repositoryName, repository.name));
+ }
closeRepository(repositoryName);
File folder = new File(repositoriesFolder, repositoryName);
File destFolder = new File(repositoriesFolder, repository.name);
@@ -657,6 +769,9 @@
"Failed to rename repository permissions ''{0}'' to ''{1}''.",
repositoryName, repository.name));
}
+
+ // clear the cache
+ clearRepositoryCache(repositoryName);
}
// load repository
@@ -697,6 +812,9 @@
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",
repository.federationStrategy.name());
config.setBoolean("gitblit", null, "isFederated", repository.isFederated);
@@ -735,6 +853,9 @@
return true;
}
}
+
+ // clear the repository cache
+ clearRepositoryCache(repositoryName);
} catch (Throwable t) {
logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
}
@@ -794,8 +915,8 @@
}
public static boolean canFederate() {
- String uuid = getString(Keys.federation.uuid, "");
- return !StringUtils.isEmpty(uuid);
+ String passphrase = getString(Keys.federation.passphrase, "");
+ return !StringUtils.isEmpty(passphrase);
}
/**
@@ -803,24 +924,31 @@
* instances.
*/
private void configureFederation() {
- boolean validUuid = true;
- String uuid = settings.getString(Keys.federation.uuid, "");
- if (StringUtils.isEmpty(uuid)) {
- logger.warn("Federation UUID is blank! This server can not be PULLED from.");
- validUuid = false;
+ boolean validPassphrase = true;
+ String passphrase = settings.getString(Keys.federation.passphrase, "");
+ if (StringUtils.isEmpty(passphrase)) {
+ logger.warn("Federation passphrase is blank! This server can not be PULLED from.");
+ validPassphrase = false;
}
- if (validUuid) {
+ if (validPassphrase) {
+ // standard tokens
for (FederationToken tokenType : FederationToken.values()) {
logger.info(MessageFormat.format("Federation {0} token = {1}", tokenType.name(),
getFederationToken(tokenType)));
+ }
+
+ // federation set tokens
+ for (String set : settings.getStrings(Keys.federation.sets)) {
+ logger.info(MessageFormat.format("Federation Set {0} token = {1}", set,
+ getFederationToken(set)));
}
}
// Schedule the federation executor
List<FederationModel> registrations = getFederationRegistrations();
if (registrations.size() > 0) {
- scheduledExecutor.schedule(new FederationPullExecutor(registrations), 1,
- TimeUnit.MINUTES);
+ FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
+ scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
}
}
@@ -832,72 +960,7 @@
*/
public List<FederationModel> getFederationRegistrations() {
if (federationRegistrations.isEmpty()) {
- List<String> keys = settings.getAllKeys(Keys.federation._ROOT);
- keys.remove(Keys.federation.name);
- keys.remove(Keys.federation.uuid);
- keys.remove(Keys.federation.allowProposals);
- keys.remove(Keys.federation.proposalsFolder);
- keys.remove(Keys.federation.defaultFrequency);
- Collections.sort(keys);
- Map<String, FederationModel> federatedModels = new HashMap<String, FederationModel>();
- for (String key : keys) {
- String value = key.substring(Keys.federation._ROOT.length() + 1);
- List<String> values = StringUtils.getStringsFromValue(value, "\\.");
- String server = values.get(0);
- if (!federatedModels.containsKey(server)) {
- federatedModels.put(server, new FederationModel(server));
- }
- String setting = values.get(1);
- if (setting.equals("url")) {
- // url of the remote Gitblit instance
- federatedModels.get(server).url = settings.getString(key, "");
- } else if (setting.equals("token")) {
- // token for the remote Gitblit instance
- federatedModels.get(server).token = settings.getString(key, "");
- } else if (setting.equals("frequency")) {
- // frequency of the pull operation
- federatedModels.get(server).frequency = settings.getString(key, "");
- } else if (setting.equals("folder")) {
- // destination folder of the pull operation
- federatedModels.get(server).folder = settings.getString(key, "");
- } else if (setting.equals("mergeAccounts")) {
- // merge remote accounts into local accounts
- federatedModels.get(server).mergeAccounts = settings.getBoolean(key, false);
- } else if (setting.equals("sendStatus")) {
- // send a status acknowledgment to source Gitblit instance
- // at end of git pull
- federatedModels.get(server).sendStatus = settings.getBoolean(key, false);
- } else if (setting.equals("notifyOnError")) {
- // notify administrators on federation pull failures
- federatedModels.get(server).notifyOnError = settings.getBoolean(key, false);
- } else if (setting.equals("exclude")) {
- // excluded repositories
- federatedModels.get(server).exclusions = settings.getStrings(key);
- } else if (setting.equals("include")) {
- // included repositories
- federatedModels.get(server).inclusions = settings.getStrings(key);
- }
- }
-
- // verify that registrations have a url and a token
- for (FederationModel model : federatedModels.values()) {
- if (StringUtils.isEmpty(model.url)) {
- logger.warn(MessageFormat.format(
- "Dropping federation registration {0}. Missing url.", model.name));
- continue;
- }
- if (StringUtils.isEmpty(model.token)) {
- logger.warn(MessageFormat.format(
- "Dropping federation registration {0}. Missing token.", model.name));
- continue;
- }
- // set default frequency if unspecified
- if (StringUtils.isEmpty(model.frequency)) {
- model.frequency = settings.getString(Keys.federation.defaultFrequency,
- "60 mins");
- }
- federationRegistrations.add(model);
- }
+ federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
}
return federationRegistrations;
}
@@ -927,14 +990,42 @@
}
/**
+ * Returns the list of federation sets.
+ *
+ * @return list of federation sets
+ */
+ public List<FederationSet> getFederationSets(String gitblitUrl) {
+ List<FederationSet> list = new ArrayList<FederationSet>();
+ // generate standard tokens
+ for (FederationToken type : FederationToken.values()) {
+ FederationSet fset = new FederationSet(type.toString(), type, getFederationToken(type));
+ fset.repositories = getRepositories(gitblitUrl, fset.token);
+ list.add(fset);
+ }
+ // generate tokens for federation sets
+ for (String set : settings.getStrings(Keys.federation.sets)) {
+ FederationSet fset = new FederationSet(set, FederationToken.REPOSITORIES,
+ getFederationToken(set));
+ fset.repositories = getRepositories(gitblitUrl, fset.token);
+ list.add(fset);
+ }
+ return list;
+ }
+
+ /**
* Returns the list of possible federation tokens for this Gitblit instance.
*
* @return list of federation tokens
*/
public List<String> getFederationTokens() {
List<String> tokens = new ArrayList<String>();
+ // generate standard tokens
for (FederationToken type : FederationToken.values()) {
tokens.add(getFederationToken(type));
+ }
+ // generate tokens for federation sets
+ for (String set : settings.getStrings(Keys.federation.sets)) {
+ tokens.add(getFederationToken(set));
}
return tokens;
}
@@ -946,8 +1037,18 @@
* @return a federation token
*/
public String getFederationToken(FederationToken type) {
- String uuid = settings.getString(Keys.federation.uuid, "");
- return StringUtils.getSHA1(uuid + "-" + type.name());
+ return getFederationToken(type.name());
+ }
+
+ /**
+ * Returns the specified federation token for this Gitblit instance.
+ *
+ * @param value
+ * @return a federation token
+ */
+ public String getFederationToken(String value) {
+ String passphrase = settings.getString(Keys.federation.passphrase, "");
+ return StringUtils.getSHA1(passphrase + "-" + value);
}
/**
@@ -1009,13 +1110,13 @@
* @param proposal
* the proposal
* @param gitblitUrl
- * the url of your gitblit instance
+ * the url of your gitblit instance to send an email to
+ * administrators
* @return true if the proposal was submitted
*/
public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
// convert proposal to json
- Gson gson = new GsonBuilder().setPrettyPrinting().create();
- String json = gson.toJson(proposal);
+ String json = JsonUtils.toJsonString(proposal);
try {
// make the proposals folder
@@ -1061,14 +1162,94 @@
&& file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
}
});
- Gson gson = new Gson();
for (File file : files) {
String json = com.gitblit.utils.FileUtils.readContent(file, null);
- FederationProposal proposal = gson.fromJson(json, FederationProposal.class);
+ FederationProposal proposal = JsonUtils.fromJsonString(json,
+ FederationProposal.class);
list.add(proposal);
}
}
return list;
+ }
+
+ /**
+ * Get repositories for the specified token.
+ *
+ * @param gitblitUrl
+ * the base url of this gitblit instance
+ * @param token
+ * the federation token
+ * @return a map of <cloneurl, RepositoryModel>
+ */
+ public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
+ Map<String, String> federationSets = new HashMap<String, String>();
+ for (String set : getStrings(Keys.federation.sets)) {
+ federationSets.put(getFederationToken(set), set);
+ }
+
+ // Determine the Gitblit clone url
+ StringBuilder sb = new StringBuilder();
+ sb.append(gitblitUrl);
+ sb.append(Constants.GIT_PATH);
+ sb.append("{0}");
+ String cloneUrl = sb.toString();
+
+ // Retrieve all available repositories
+ UserModel user = new UserModel(Constants.FEDERATION_USER);
+ user.canAdmin = true;
+ List<RepositoryModel> list = getRepositoryModels(user);
+
+ // create the [cloneurl, repositoryModel] map
+ Map<String, RepositoryModel> repositories = new HashMap<String, RepositoryModel>();
+ for (RepositoryModel model : list) {
+ // by default, setup the url for THIS repository
+ String url = MessageFormat.format(cloneUrl, model.name);
+ switch (model.federationStrategy) {
+ case EXCLUDE:
+ // skip this repository
+ continue;
+ case FEDERATE_ORIGIN:
+ // federate the origin, if it is defined
+ if (!StringUtils.isEmpty(model.origin)) {
+ url = model.origin;
+ }
+ break;
+ }
+
+ if (federationSets.containsKey(token)) {
+ // include repositories only for federation set
+ String set = federationSets.get(token);
+ if (model.federationSets.contains(set)) {
+ repositories.put(url, model);
+ }
+ } else {
+ // standard federation token for ALL
+ repositories.put(url, model);
+ }
+ }
+ return repositories;
+ }
+
+ /**
+ * Creates a proposal from the token.
+ *
+ * @param gitblitUrl
+ * the url of this Gitblit instance
+ * @param token
+ * @return a potential proposal
+ */
+ public FederationProposal createFederationProposal(String gitblitUrl, String token) {
+ FederationToken tokenType = FederationToken.REPOSITORIES;
+ for (FederationToken type : FederationToken.values()) {
+ if (token.equals(getFederationToken(type))) {
+ tokenType = type;
+ break;
+ }
+ }
+ Map<String, RepositoryModel> repositories = getRepositories(gitblitUrl, token);
+ FederationProposal proposal = new FederationProposal(gitblitUrl, tokenType, token,
+ repositories);
+ return proposal;
}
/**
@@ -1120,18 +1301,95 @@
}
/**
+ * Returns the descriptions/comments of the Gitblit config settings.
+ *
+ * @return SettingsModel
+ */
+ public ServerSettings getSettingsModel() {
+ // ensure that the current values are updated in the setting models
+ settingsModel.updateCurrentValues(settings);
+ return settingsModel;
+ }
+
+ /**
+ * Parse the properties file and aggregate all the comments by the setting
+ * key. A setting model tracks the current value, the default value, the
+ * description of the setting and and directives about the setting.
+ *
+ * @return Map<String, SettingModel>
+ */
+ private ServerSettings loadSettingModels() {
+ ServerSettings settingsModel = new ServerSettings();
+ try {
+ // Read bundled Gitblit properties to extract setting descriptions.
+ // This copy is pristine and only used for populating the setting
+ // models map.
+ InputStream is = servletContext.getResourceAsStream("/WEB-INF/reference.properties");
+ BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
+ StringBuilder description = new StringBuilder();
+ SettingModel setting = new SettingModel();
+ String line = null;
+ while ((line = propertiesReader.readLine()) != null) {
+ if (line.length() == 0) {
+ description.setLength(0);
+ setting = new SettingModel();
+ } else {
+ if (line.charAt(0) == '#') {
+ if (line.length() > 1) {
+ String text = line.substring(1).trim();
+ if (SettingModel.CASE_SENSITIVE.equals(text)) {
+ setting.caseSensitive = true;
+ } else if (SettingModel.RESTART_REQUIRED.equals(text)) {
+ setting.restartRequired = true;
+ } else if (SettingModel.SPACE_DELIMITED.equals(text)) {
+ setting.spaceDelimited = true;
+ } else if (text.startsWith(SettingModel.SINCE)) {
+ try {
+ setting.since = text.split(" ")[1];
+ } catch (Exception e) {
+ setting.since = text;
+ }
+ } else {
+ description.append(text);
+ description.append('\n');
+ }
+ }
+ } else {
+ String[] kvp = line.split("=", 2);
+ String key = kvp[0].trim();
+ setting.name = key;
+ setting.defaultValue = kvp[1].trim();
+ setting.currentValue = setting.defaultValue;
+ setting.description = description.toString().trim();
+ settingsModel.add(setting);
+ description.setLength(0);
+ setting = new SettingModel();
+ }
+ }
+ }
+ propertiesReader.close();
+ } catch (NullPointerException e) {
+ logger.error("Failed to find resource copy of gitblit.properties");
+ } catch (IOException e) {
+ logger.error("Failed to load resource copy of gitblit.properties");
+ }
+ return settingsModel;
+ }
+
+ /**
* Configure the Gitblit singleton with the specified settings source. This
* source may be file settings (Gitblit GO) or may be web.xml settings
* (Gitblit WAR).
*
* @param settings
*/
- public void configureContext(IStoredSettings settings) {
+ public void configureContext(IStoredSettings settings, boolean startFederation) {
logger.info("Reading configuration from " + settings.toString());
this.settings = settings;
repositoriesFolder = new File(settings.getString(Keys.git.repositoriesFolder, "git"));
logger.info("Git repositories folder " + repositoriesFolder.getAbsolutePath());
repositoryResolver = new FileResolver<Void>(repositoriesFolder, exportAll);
+ serverStatus = new ServerStatus(isGO());
String realm = settings.getString(Keys.realm.userService, "users.properties");
IUserService loginService = null;
try {
@@ -1155,12 +1413,14 @@
loginService = new FileUserService(realmFile);
}
setUserService(loginService);
- configureFederation();
mailExecutor = new MailExecutor(settings);
if (mailExecutor.isReady()) {
scheduledExecutor.scheduleAtFixedRate(mailExecutor, 1, 2, TimeUnit.MINUTES);
} else {
logger.warn("Mail server is not properly configured. Mail services disabled.");
+ }
+ if (startFederation) {
+ configureFederation();
}
}
@@ -1172,11 +1432,15 @@
*/
@Override
public void contextInitialized(ServletContextEvent contextEvent) {
+ servletContext = contextEvent.getServletContext();
+ settingsModel = loadSettingModels();
if (settings == null) {
// Gitblit WAR is running in a servlet container
WebXmlSettings webxmlSettings = new WebXmlSettings(contextEvent.getServletContext());
- configureContext(webxmlSettings);
+ configureContext(webxmlSettings, true);
}
+
+ serverStatus.servletContainer = servletContext.getServerInfo();
}
/**
--
Gitblit v1.9.1