summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--releases.moxie1
-rw-r--r--src/main/java/com/gitblit/ConfigUserService.java21
-rw-r--r--src/main/java/com/gitblit/GitBlit.java7312
-rw-r--r--src/main/java/com/gitblit/IStoredSettings.java2
-rw-r--r--src/main/java/com/gitblit/models/UserModel.java8
-rw-r--r--src/main/java/com/gitblit/models/UserPreferences.java91
-rw-r--r--src/main/java/com/gitblit/models/UserRepositoryPreferences.java40
-rw-r--r--src/main/java/com/gitblit/wicket/GitBlitWebApp.properties948
-rw-r--r--src/main/java/com/gitblit/wicket/pages/RepositoryPage.html9
-rw-r--r--src/main/java/com/gitblit/wicket/pages/RepositoryPage.java58
10 files changed, 4364 insertions, 4126 deletions
diff --git a/releases.moxie b/releases.moxie
index 24eba09d..929721d3 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -39,6 +39,7 @@ r17: {
- Updated Japanese translation
additions:
+ - Added simple star/unstar function to flag or bookmark interesting repositories
- Added a ui for the push log introduced in 1.2.1 (issue-177)
- Added client application menus for Git, SourceTree, Tower, GitHub for Windows, GitHub for Mac, and SparkleShare
- Added GO http/https connector thread pool size setting
diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java
index 7aa09985..f2bd7b82 100644
--- a/src/main/java/com/gitblit/ConfigUserService.java
+++ b/src/main/java/com/gitblit/ConfigUserService.java
@@ -37,6 +37,7 @@ import org.slf4j.LoggerFactory;
import com.gitblit.Constants.AccessPermission;
import com.gitblit.models.TeamModel;
import com.gitblit.models.UserModel;
+import com.gitblit.models.UserRepositoryPreferences;
import com.gitblit.utils.ArrayUtils;
import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.StringUtils;
@@ -88,7 +89,9 @@ public class ConfigUserService implements IUserService {
private static final String PRERECEIVE = "preReceiveScript";
private static final String POSTRECEIVE = "postReceiveScript";
-
+
+ private static final String STARRED = "starred";
+
private final File realmFile;
private final Logger logger = LoggerFactory.getLogger(ConfigUserService.class);
@@ -879,6 +882,14 @@ public class ConfigUserService implements IUserService {
}
config.setStringList(USER, model.username, REPOSITORY, permissions);
}
+
+ // user preferences
+ if (model.getPreferences() != null) {
+ List<String> starred = model.getPreferences().getStarredRepositories();
+ if (starred.size() > 0) {
+ config.setStringList(USER, model.username, STARRED, starred);
+ }
+ }
}
// write teams
@@ -1021,6 +1032,14 @@ public class ConfigUserService implements IUserService {
}
}
+ // starred repositories
+ Set<String> starred = new HashSet<String>(Arrays.asList(config
+ .getStringList(USER, username, STARRED)));
+ for (String repository : starred) {
+ UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(repository);
+ prefs.starred = true;
+ }
+
// update cache
users.put(user.username, user);
if (!StringUtils.isEmpty(user.cookie)) {
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index 081b74a4..c5616d93 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -1,3540 +1,3556 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit;
-
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileFilter;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.lang.reflect.Field;
-import java.lang.reflect.Type;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.charset.Charset;
-import java.security.Principal;
-import java.text.MessageFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-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;
-import javax.mail.internet.MimeBodyPart;
-import javax.mail.internet.MimeMultipart;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-
-import org.apache.wicket.RequestCycle;
-import org.apache.wicket.protocol.http.WebResponse;
-import org.apache.wicket.resource.ContextRelativeResource;
-import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.RepositoryCache;
-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.WindowCacheConfig;
-import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.FileUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.AuthenticationType;
-import com.gitblit.Constants.AuthorizationControl;
-import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationStrategy;
-import com.gitblit.Constants.FederationToken;
-import com.gitblit.Constants.PermissionType;
-import com.gitblit.Constants.RegistrantType;
-import com.gitblit.fanout.FanoutNioService;
-import com.gitblit.fanout.FanoutService;
-import com.gitblit.fanout.FanoutSocketService;
-import com.gitblit.git.GitDaemon;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.FederationProposal;
-import com.gitblit.models.FederationSet;
-import com.gitblit.models.ForkModel;
-import com.gitblit.models.GitClientApplication;
-import com.gitblit.models.Metric;
-import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RegistrantAccessPermission;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
-import com.gitblit.models.SearchResult;
-import com.gitblit.models.ServerSettings;
-import com.gitblit.models.ServerStatus;
-import com.gitblit.models.SettingModel;
-import com.gitblit.models.TeamModel;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.Base64;
-import com.gitblit.utils.ByteFormat;
-import com.gitblit.utils.ContainerUtils;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.HttpUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.MetricUtils;
-import com.gitblit.utils.ObjectCache;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
-import com.gitblit.utils.X509Utils.X509Metadata;
-import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.WicketUtils;
-import com.google.gson.Gson;
-import com.google.gson.JsonIOException;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.reflect.TypeToken;
-
-/**
- * GitBlit is the servlet context listener singleton that acts as the core for
- * the web ui and the servlets. This class is either directly instantiated by
- * the GitBlitServer class (Gitblit GO) or is reflectively instantiated from the
- * definition in the web.xml file (Gitblit WAR).
- *
- * This class is the central logic processor for Gitblit. All settings, user
- * object, and repository object operations pass through this class.
- *
- * Repository Resolution. There are two pathways for finding repositories. One
- * pathway, for web ui display and repository authentication & authorization, is
- * within this class. The other pathway is through the standard GitServlet.
- *
- * @author James Moger
- *
- */
-public class GitBlit implements ServletContextListener {
-
- private static GitBlit gitblit;
-
- private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
-
- private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
-
- private final List<FederationModel> federationRegistrations = Collections
- .synchronizedList(new ArrayList<FederationModel>());
-
- private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
-
- 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 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 final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
-
- private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
-
- private ServletContext servletContext;
-
- private File baseFolder;
-
- private File repositoriesFolder;
-
- private IUserService userService;
-
- private IStoredSettings settings;
-
- private ServerSettings settingsModel;
-
- private ServerStatus serverStatus;
-
- private MailExecutor mailExecutor;
-
- private LuceneExecutor luceneExecutor;
-
- private GCExecutor gcExecutor;
-
- private TimeZone timezone;
-
- private FileBasedConfig projectConfigs;
-
- private FanoutService fanoutService;
-
- private GitDaemon gitDaemon;
-
- public GitBlit() {
- if (gitblit == null) {
- // set the static singleton reference
- gitblit = this;
- }
- }
-
- public GitBlit(final IUserService userService) {
- this.userService = userService;
- gitblit = this;
- }
-
- /**
- * Returns the Gitblit singleton.
- *
- * @return gitblit singleton
- */
- public static GitBlit self() {
- if (gitblit == null) {
- new GitBlit();
- }
- return gitblit;
- }
-
- /**
- * Determine if this is the GO variant of Gitblit.
- *
- * @return true if this is the GO variant of Gitblit.
- */
- public static boolean isGO() {
- return self().settings instanceof FileSettings;
- }
-
- /**
- * Returns the preferred timezone for the Gitblit instance.
- *
- * @return a timezone
- */
- public static TimeZone getTimezone() {
- if (self().timezone == null) {
- String tzid = getString("web.timezone", null);
- if (StringUtils.isEmpty(tzid)) {
- self().timezone = TimeZone.getDefault();
- return self().timezone;
- }
- self().timezone = TimeZone.getTimeZone(tzid);
- }
- 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
- * exist or the value for the key can not be interpreted as a boolean, the
- * defaultValue is returned.
- *
- * @see IStoredSettings.getBoolean(String, boolean)
- * @param key
- * @param defaultValue
- * @return key value or defaultValue
- */
- public static boolean getBoolean(String key, boolean defaultValue) {
- return self().settings.getBoolean(key, defaultValue);
- }
-
- /**
- * Returns the integer value for the specified key. If the key does not
- * exist or the value for the key can not be interpreted as an integer, the
- * defaultValue is returned.
- *
- * @see IStoredSettings.getInteger(String key, int defaultValue)
- * @param key
- * @param defaultValue
- * @return key value or defaultValue
- */
- public static int getInteger(String key, int defaultValue) {
- return self().settings.getInteger(key, defaultValue);
- }
-
- /**
- * Returns the value in bytes for the specified key. If the key does not
- * exist or the value for the key can not be interpreted as an integer, the
- * defaultValue is returned.
- *
- * @see IStoredSettings.getFilesize(String key, int defaultValue)
- * @param key
- * @param defaultValue
- * @return key value or defaultValue
- */
- public static int getFilesize(String key, int defaultValue) {
- return self().settings.getFilesize(key, defaultValue);
- }
-
- /**
- * Returns the value in bytes for the specified key. If the key does not
- * exist or the value for the key can not be interpreted as a long, the
- * defaultValue is returned.
- *
- * @see IStoredSettings.getFilesize(String key, long defaultValue)
- * @param key
- * @param defaultValue
- * @return key value or defaultValue
- */
- public static long getFilesize(String key, long defaultValue) {
- return self().settings.getFilesize(key, defaultValue);
- }
-
- /**
- * Returns the char value for the specified key. If the key does not exist
- * or the value for the key can not be interpreted as a character, the
- * defaultValue is returned.
- *
- * @see IStoredSettings.getChar(String key, char defaultValue)
- * @param key
- * @param defaultValue
- * @return key value or defaultValue
- */
- public static char getChar(String key, char defaultValue) {
- return self().settings.getChar(key, defaultValue);
- }
-
- /**
- * Returns the string value for the specified key. If the key does not exist
- * or the value for the key can not be interpreted as a string, the
- * defaultValue is returned.
- *
- * @see IStoredSettings.getString(String key, String defaultValue)
- * @param key
- * @param defaultValue
- * @return key value or defaultValue
- */
- public static String getString(String key, String defaultValue) {
- return self().settings.getString(key, defaultValue);
- }
-
- /**
- * Returns a list of space-separated strings from the specified key.
- *
- * @see IStoredSettings.getStrings(String key)
- * @param name
- * @return list of strings
- */
- 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);
- }
-
- /**
- * Returns the list of keys whose name starts with the specified prefix. If
- * the prefix is null or empty, all key names are returned.
- *
- * @see IStoredSettings.getAllKeys(String key)
- * @param startingWith
- * @return list of keys
- */
-
- public static List<String> getAllKeys(String startingWith) {
- return self().settings.getAllKeys(startingWith);
- }
-
- /**
- * Is Gitblit running in debug mode?
- *
- * @return true if Gitblit is running in debug mode
- */
- public static boolean isDebugMode() {
- return self().settings.getBoolean(Keys.web.debugMode, false);
- }
-
- /**
- * Returns the file object for the specified configuration key.
- *
- * @return the file
- */
- public static File getFileOrFolder(String key, String defaultFileOrFolder) {
- String fileOrFolder = GitBlit.getString(key, defaultFileOrFolder);
- return getFileOrFolder(fileOrFolder);
- }
-
- /**
- * Returns the file object which may have it's base-path determined by
- * environment variables for running on a cloud hosting service. All Gitblit
- * file or folder retrievals are (at least initially) funneled through this
- * method so it is the correct point to globally override/alter filesystem
- * access based on environment or some other indicator.
- *
- * @return the file
- */
- public static File getFileOrFolder(String fileOrFolder) {
- return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
- self().baseFolder, fileOrFolder);
- }
-
- /**
- * Returns the path of the repositories folder. This method checks to see if
- * Gitblit is running on a cloud service and may return an adjusted path.
- *
- * @return the repositories folder path
- */
- public static File getRepositoriesFolder() {
- return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
- }
-
- /**
- * Returns the path of the proposals folder. This method checks to see if
- * Gitblit is running on a cloud service and may return an adjusted path.
- *
- * @return the proposals folder path
- */
- public static File getProposalsFolder() {
- return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
- }
-
- /**
- * Returns the path of the Groovy folder. This method checks to see if
- * Gitblit is running on a cloud service and may return an adjusted path.
- *
- * @return the Groovy scripts folder path
- */
- public static File getGroovyScriptsFolder() {
- return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
- }
-
- /**
- * 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 a list of repository URLs and the user access permission.
- *
- * @param request
- * @param user
- * @param repository
- * @return a list of repository urls
- */
- public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
- if (user == null) {
- user = UserModel.ANONYMOUS;
- }
- String username = UserModel.ANONYMOUS.equals(user) ? "" : user.username;
-
- List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
- // http/https url
- if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
- AccessPermission permission = user.getRepositoryPermission(repository).permission;
- if (permission.exceeds(AccessPermission.NONE)) {
- list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
- }
- }
-
- // git daemon url
- String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
- if (!StringUtils.isEmpty(gitDaemonUrl)) {
- AccessPermission permission = getGitDaemonAccessPermission(user, repository);
- if (permission.exceeds(AccessPermission.NONE)) {
- list.add(new RepositoryUrl(gitDaemonUrl, permission));
- }
- }
-
- // add all other urls
- // {0} = repository
- // {1} = username
- for (String url : settings.getStrings(Keys.web.otherUrls)) {
- if (url.contains("{1}")) {
- // external url requires username, only add url IF we have one
- if(!StringUtils.isEmpty(username)) {
- list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
- }
- } else {
- // external url does not require username
- list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
- }
- }
- return list;
- }
-
- protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
- StringBuilder sb = new StringBuilder();
- sb.append(HttpUtils.getGitblitURL(request));
- sb.append(Constants.GIT_PATH);
- sb.append(repository.name);
-
- // inject username into repository url if authentication is required
- if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
- && !StringUtils.isEmpty(username)) {
- sb.insert(sb.indexOf("://") + 3, username + "@");
- }
- return sb.toString();
- }
-
- protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
- if (gitDaemon != null) {
- String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
- if (bindInterface.equals("localhost")
- && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
- // git daemon is bound to localhost and the request is from elsewhere
- return null;
- }
- if (user.canClone(repository)) {
- String servername = request.getServerName();
- String url = gitDaemon.formatUrl(servername, repository.name);
- return url;
- }
- }
- return null;
- }
-
- protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
- if (gitDaemon != null && user.canClone(repository)) {
- AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
- if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
- if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
- // can not authenticate clone via anonymous git protocol
- gitDaemonPermission = AccessPermission.NONE;
- } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
- // can not authenticate push via anonymous git protocol
- gitDaemonPermission = AccessPermission.CLONE;
- } else {
- // normal user permission
- }
- }
- return gitDaemonPermission;
- }
- return AccessPermission.NONE;
- }
-
- /**
- * Returns the list of custom client applications to be used for the
- * repository url panel;
- *
- * @return a collection of client applications
- */
- public Collection<GitClientApplication> getClientApplications() {
- // prefer user definitions, if they exist
- File userDefs = new File(baseFolder, "clientapps.json");
- if (userDefs.exists()) {
- Date lastModified = new Date(userDefs.lastModified());
- if (clientApplications.hasCurrent("user", lastModified)) {
- return clientApplications.getObject("user");
- } else {
- // (re)load user definitions
- try {
- InputStream is = new FileInputStream(userDefs);
- Collection<GitClientApplication> clients = readClientApplications(is);
- is.close();
- if (clients != null) {
- clientApplications.updateObject("user", lastModified, clients);
- return clients;
- }
- } catch (IOException e) {
- logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
- }
- }
- }
-
- // no user definitions, use system definitions
- if (!clientApplications.hasCurrent("system", new Date(0))) {
- try {
- InputStream is = getClass().getResourceAsStream("/clientapps.json");
- Collection<GitClientApplication> clients = readClientApplications(is);
- is.close();
- if (clients != null) {
- clientApplications.updateObject("system", new Date(0), clients);
- }
- } catch (IOException e) {
- logger.error("Failed to deserialize clientapps.json resource!", e);
- }
- }
-
- return clientApplications.getObject("system");
- }
-
- private Collection<GitClientApplication> readClientApplications(InputStream is) {
- try {
- Type type = new TypeToken<Collection<GitClientApplication>>() {
- }.getType();
- InputStreamReader reader = new InputStreamReader(is);
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Type;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.Charset;
+import java.security.Principal;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+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;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMultipart;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.RequestCycle;
+import org.apache.wicket.protocol.http.WebResponse;
+import org.apache.wicket.resource.ContextRelativeResource;
+import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.RepositoryCache;
+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.WindowCacheConfig;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.FileUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthenticationType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationRequest;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.fanout.FanoutNioService;
+import com.gitblit.fanout.FanoutService;
+import com.gitblit.fanout.FanoutSocketService;
+import com.gitblit.git.GitDaemon;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FederationProposal;
+import com.gitblit.models.FederationSet;
+import com.gitblit.models.ForkModel;
+import com.gitblit.models.GitClientApplication;
+import com.gitblit.models.Metric;
+import com.gitblit.models.ProjectModel;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.SearchResult;
+import com.gitblit.models.ServerSettings;
+import com.gitblit.models.ServerStatus;
+import com.gitblit.models.SettingModel;
+import com.gitblit.models.TeamModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.Base64;
+import com.gitblit.utils.ByteFormat;
+import com.gitblit.utils.ContainerUtils;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.HttpUtils;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.JsonUtils;
+import com.gitblit.utils.MetricUtils;
+import com.gitblit.utils.ObjectCache;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.utils.X509Utils.X509Metadata;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.WicketUtils;
+import com.google.gson.Gson;
+import com.google.gson.JsonIOException;
+import com.google.gson.JsonSyntaxException;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * GitBlit is the servlet context listener singleton that acts as the core for
+ * the web ui and the servlets. This class is either directly instantiated by
+ * the GitBlitServer class (Gitblit GO) or is reflectively instantiated from the
+ * definition in the web.xml file (Gitblit WAR).
+ *
+ * This class is the central logic processor for Gitblit. All settings, user
+ * object, and repository object operations pass through this class.
+ *
+ * Repository Resolution. There are two pathways for finding repositories. One
+ * pathway, for web ui display and repository authentication & authorization, is
+ * within this class. The other pathway is through the standard GitServlet.
+ *
+ * @author James Moger
+ *
+ */
+public class GitBlit implements ServletContextListener {
+
+ private static GitBlit gitblit;
+
+ private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
+
+ private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
+
+ private final List<FederationModel> federationRegistrations = Collections
+ .synchronizedList(new ArrayList<FederationModel>());
+
+ private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
+
+ 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 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 final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
+
+ private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
+
+ private ServletContext servletContext;
+
+ private File baseFolder;
+
+ private File repositoriesFolder;
+
+ private IUserService userService;
+
+ private IStoredSettings settings;
+
+ private ServerSettings settingsModel;
+
+ private ServerStatus serverStatus;
+
+ private MailExecutor mailExecutor;
+
+ private LuceneExecutor luceneExecutor;
+
+ private GCExecutor gcExecutor;
+
+ private TimeZone timezone;
+
+ private FileBasedConfig projectConfigs;
+
+ private FanoutService fanoutService;
+
+ private GitDaemon gitDaemon;
+
+ public GitBlit() {
+ if (gitblit == null) {
+ // set the static singleton reference
+ gitblit = this;
+ }
+ }
+
+ public GitBlit(final IUserService userService) {
+ this.userService = userService;
+ gitblit = this;
+ }
+
+ /**
+ * Returns the Gitblit singleton.
+ *
+ * @return gitblit singleton
+ */
+ public static GitBlit self() {
+ if (gitblit == null) {
+ new GitBlit();
+ }
+ return gitblit;
+ }
+
+ /**
+ * Determine if this is the GO variant of Gitblit.
+ *
+ * @return true if this is the GO variant of Gitblit.
+ */
+ public static boolean isGO() {
+ return self().settings instanceof FileSettings;
+ }
+
+ /**
+ * Returns the preferred timezone for the Gitblit instance.
+ *
+ * @return a timezone
+ */
+ public static TimeZone getTimezone() {
+ if (self().timezone == null) {
+ String tzid = getString("web.timezone", null);
+ if (StringUtils.isEmpty(tzid)) {
+ self().timezone = TimeZone.getDefault();
+ return self().timezone;
+ }
+ self().timezone = TimeZone.getTimeZone(tzid);
+ }
+ 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
+ * exist or the value for the key can not be interpreted as a boolean, the
+ * defaultValue is returned.
+ *
+ * @see IStoredSettings.getBoolean(String, boolean)
+ * @param key
+ * @param defaultValue
+ * @return key value or defaultValue
+ */
+ public static boolean getBoolean(String key, boolean defaultValue) {
+ return self().settings.getBoolean(key, defaultValue);
+ }
+
+ /**
+ * Returns the integer value for the specified key. If the key does not
+ * exist or the value for the key can not be interpreted as an integer, the
+ * defaultValue is returned.
+ *
+ * @see IStoredSettings.getInteger(String key, int defaultValue)
+ * @param key
+ * @param defaultValue
+ * @return key value or defaultValue
+ */
+ public static int getInteger(String key, int defaultValue) {
+ return self().settings.getInteger(key, defaultValue);
+ }
+
+ /**
+ * Returns the value in bytes for the specified key. If the key does not
+ * exist or the value for the key can not be interpreted as an integer, the
+ * defaultValue is returned.
+ *
+ * @see IStoredSettings.getFilesize(String key, int defaultValue)
+ * @param key
+ * @param defaultValue
+ * @return key value or defaultValue
+ */
+ public static int getFilesize(String key, int defaultValue) {
+ return self().settings.getFilesize(key, defaultValue);
+ }
+
+ /**
+ * Returns the value in bytes for the specified key. If the key does not
+ * exist or the value for the key can not be interpreted as a long, the
+ * defaultValue is returned.
+ *
+ * @see IStoredSettings.getFilesize(String key, long defaultValue)
+ * @param key
+ * @param defaultValue
+ * @return key value or defaultValue
+ */
+ public static long getFilesize(String key, long defaultValue) {
+ return self().settings.getFilesize(key, defaultValue);
+ }
+
+ /**
+ * Returns the char value for the specified key. If the key does not exist
+ * or the value for the key can not be interpreted as a character, the
+ * defaultValue is returned.
+ *
+ * @see IStoredSettings.getChar(String key, char defaultValue)
+ * @param key
+ * @param defaultValue
+ * @return key value or defaultValue
+ */
+ public static char getChar(String key, char defaultValue) {
+ return self().settings.getChar(key, defaultValue);
+ }
+
+ /**
+ * Returns the string value for the specified key. If the key does not exist
+ * or the value for the key can not be interpreted as a string, the
+ * defaultValue is returned.
+ *
+ * @see IStoredSettings.getString(String key, String defaultValue)
+ * @param key
+ * @param defaultValue
+ * @return key value or defaultValue
+ */
+ public static String getString(String key, String defaultValue) {
+ return self().settings.getString(key, defaultValue);
+ }
+
+ /**
+ * Returns a list of space-separated strings from the specified key.
+ *
+ * @see IStoredSettings.getStrings(String key)
+ * @param n
+ * @return list of strings
+ */
+ 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 n
+ * @return map of string, string
+ */
+ public static Map<String, String> getMap(String key) {
+ return self().settings.getMap(key);
+ }
+
+ /**
+ * Returns the list of keys whose name starts with the specified prefix. If
+ * the prefix is null or empty, all key names are returned.
+ *
+ * @see IStoredSettings.getAllKeys(String key)
+ * @param startingWith
+ * @return list of keys
+ */
+
+ public static List<String> getAllKeys(String startingWith) {
+ return self().settings.getAllKeys(startingWith);
+ }
+
+ /**
+ * Is Gitblit running in debug mode?
+ *
+ * @return true if Gitblit is running in debug mode
+ */
+ public static boolean isDebugMode() {
+ return self().settings.getBoolean(Keys.web.debugMode, false);
+ }
+
+ /**
+ * Returns the file object for the specified configuration key.
+ *
+ * @return the file
+ */
+ public static File getFileOrFolder(String key, String defaultFileOrFolder) {
+ String fileOrFolder = GitBlit.getString(key, defaultFileOrFolder);
+ return getFileOrFolder(fileOrFolder);
+ }
+
+ /**
+ * Returns the file object which may have it's base-path determined by
+ * environment variables for running on a cloud hosting service. All Gitblit
+ * file or folder retrievals are (at least initially) funneled through this
+ * method so it is the correct point to globally override/alter filesystem
+ * access based on environment or some other indicator.
+ *
+ * @return the file
+ */
+ public static File getFileOrFolder(String fileOrFolder) {
+ return com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$,
+ self().baseFolder, fileOrFolder);
+ }
+
+ /**
+ * Returns the path of the repositories folder. This method checks to see if
+ * Gitblit is running on a cloud service and may return an adjusted path.
+ *
+ * @return the repositories folder path
+ */
+ public static File getRepositoriesFolder() {
+ return getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
+ }
+
+ /**
+ * Returns the path of the proposals folder. This method checks to see if
+ * Gitblit is running on a cloud service and may return an adjusted path.
+ *
+ * @return the proposals folder path
+ */
+ public static File getProposalsFolder() {
+ return getFileOrFolder(Keys.federation.proposalsFolder, "${baseFolder}/proposals");
+ }
+
+ /**
+ * Returns the path of the Groovy folder. This method checks to see if
+ * Gitblit is running on a cloud service and may return an adjusted path.
+ *
+ * @return the Groovy scripts folder path
+ */
+ public static File getGroovyScriptsFolder() {
+ return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
+ }
+
+ /**
+ * 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 a list of repository URLs and the user access permission.
+ *
+ * @param request
+ * @param user
+ * @param repository
+ * @return a list of repository urls
+ */
+ public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ String username = UserModel.ANONYMOUS.equals(user) ? "" : user.username;
+
+ List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
+ // http/https url
+ if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
+ AccessPermission permission = user.getRepositoryPermission(repository).permission;
+ if (permission.exceeds(AccessPermission.NONE)) {
+ list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
+ }
+ }
+
+ // git daemon url
+ String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
+ if (!StringUtils.isEmpty(gitDaemonUrl)) {
+ AccessPermission permission = getGitDaemonAccessPermission(user, repository);
+ if (permission.exceeds(AccessPermission.NONE)) {
+ list.add(new RepositoryUrl(gitDaemonUrl, permission));
+ }
+ }
+
+ // add all other urls
+ // {0} = repository
+ // {1} = username
+ for (String url : settings.getStrings(Keys.web.otherUrls)) {
+ if (url.contains("{1}")) {
+ // external url requires username, only add url IF we have one
+ if(!StringUtils.isEmpty(username)) {
+ list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
+ }
+ } else {
+ // external url does not require username
+ list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
+ }
+ }
+ return list;
+ }
+
+ protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(HttpUtils.getGitblitURL(request));
+ sb.append(Constants.GIT_PATH);
+ sb.append(repository.name);
+
+ // inject username into repository url if authentication is required
+ if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
+ && !StringUtils.isEmpty(username)) {
+ sb.insert(sb.indexOf("://") + 3, username + "@");
+ }
+ return sb.toString();
+ }
+
+ protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+ if (gitDaemon != null) {
+ String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
+ if (bindInterface.equals("localhost")
+ && (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
+ // git daemon is bound to localhost and the request is from elsewhere
+ return null;
+ }
+ if (user.canClone(repository)) {
+ String servername = request.getServerName();
+ String url = gitDaemon.formatUrl(servername, repository.name);
+ return url;
+ }
+ }
+ return null;
+ }
+
+ protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
+ if (gitDaemon != null && user.canClone(repository)) {
+ AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
+ if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
+ if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
+ // can not authenticate clone via anonymous git protocol
+ gitDaemonPermission = AccessPermission.NONE;
+ } else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+ // can not authenticate push via anonymous git protocol
+ gitDaemonPermission = AccessPermission.CLONE;
+ } else {
+ // normal user permission
+ }
+ }
+ return gitDaemonPermission;
+ }
+ return AccessPermission.NONE;
+ }
+
+ /**
+ * Returns the list of custom client applications to be used for the
+ * repository url panel;
+ *
+ * @return a collection of client applications
+ */
+ public Collection<GitClientApplication> getClientApplications() {
+ // prefer user definitions, if they exist
+ File userDefs = new File(baseFolder, "clientapps.json");
+ if (userDefs.exists()) {
+ Date lastModified = new Date(userDefs.lastModified());
+ if (clientApplications.hasCurrent("user", lastModified)) {
+ return clientApplications.getObject("user");
+ } else {
+ // (re)load user definitions
+ try {
+ InputStream is = new FileInputStream(userDefs);
+ Collection<GitClientApplication> clients = readClientApplications(is);
+ is.close();
+ if (clients != null) {
+ clientApplications.updateObject("user", lastModified, clients);
+ return clients;
+ }
+ } catch (IOException e) {
+ logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
+ }
+ }
+ }
+
+ // no user definitions, use system definitions
+ if (!clientApplications.hasCurrent("system", new Date(0))) {
+ try {
+ InputStream is = getClass().getResourceAsStream("/clientapps.json");
+ Collection<GitClientApplication> clients = readClientApplications(is);
+ is.close();
+ if (clients != null) {
+ clientApplications.updateObject("system", new Date(0), clients);
+ }
+ } catch (IOException e) {
+ logger.error("Failed to deserialize clientapps.json resource!", e);
+ }
+ }
+
+ return clientApplications.getObject("system");
+ }
+
+ private Collection<GitClientApplication> readClientApplications(InputStream is) {
+ try {
+ Type type = new TypeToken<Collection<GitClientApplication>>() {
+ }.getType();
+ InputStreamReader reader = new InputStreamReader(is);
Gson gson = JsonUtils.gson();
- Collection<GitClientApplication> links = gson.fromJson(reader, type);
- return links;
- } catch (JsonIOException e) {
- logger.error("Error deserializing client applications!", e);
- } catch (JsonSyntaxException e) {
- logger.error("Error deserializing client applications!", e);
- }
- return null;
- }
-
- /**
- * Set the user service. The user service authenticates all users and is
- * responsible for managing user permissions.
- *
- * @param userService
- */
- public void setUserService(IUserService userService) {
- logger.info("Setting up user service " + userService.toString());
- this.userService = userService;
- this.userService.setup(settings);
- }
-
- public boolean supportsAddUser() {
- return supportsCredentialChanges(new UserModel(""));
- }
-
- /**
- * Returns true if the user's credentials can be changed.
- *
- * @param user
- * @return true if the user service supports credential changes
- */
- public boolean supportsCredentialChanges(UserModel user) {
- return (user != null && user.isLocalAccount()) || userService.supportsCredentialChanges();
- }
-
- /**
- * Returns true if the user's display name can be changed.
- *
- * @param user
- * @return true if the user service supports display name changes
- */
- public boolean supportsDisplayNameChanges(UserModel user) {
- return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
- }
-
- /**
- * Returns true if the user's email address can be changed.
- *
- * @param user
- * @return true if the user service supports email address changes
- */
- public boolean supportsEmailAddressChanges(UserModel user) {
- return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
- }
-
- /**
- * Returns true if the user's team memberships can be changed.
- *
- * @param user
- * @return true if the user service supports team membership changes
- */
- public boolean supportsTeamMembershipChanges(UserModel user) {
- return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
- }
-
- /**
- * Authenticate a user based on a username and password.
- *
- * @see IUserService.authenticate(String, char[])
- * @param username
- * @param password
- * @return a user object or null
- */
- public UserModel authenticate(String username, char[] password) {
- if (StringUtils.isEmpty(username)) {
- // can not authenticate empty username
- return null;
- }
- String pw = new String(password);
- if (StringUtils.isEmpty(pw)) {
- // can not authenticate empty password
- return null;
- }
-
- // check to see if this is the federation user
- if (canFederate()) {
- if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
- List<String> tokens = getFederationTokens();
- if (tokens.contains(pw)) {
- // the federation user is an administrator
- UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
- federationUser.canAdmin = true;
- return federationUser;
- }
- }
- }
-
- // delegate authentication to the user service
- if (userService == null) {
- return null;
- }
- return userService.authenticate(username, password);
- }
-
- /**
- * Authenticate a user based on their cookie.
- *
- * @param cookies
- * @return a user object or null
- */
- protected UserModel authenticate(Cookie[] cookies) {
- if (userService == null) {
- return null;
- }
- if (userService.supportsCookies()) {
- if (cookies != null && cookies.length > 0) {
- for (Cookie cookie : cookies) {
- if (cookie.getName().equals(Constants.NAME)) {
- String value = cookie.getValue();
- return userService.authenticate(value.toCharArray());
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Authenticate a user based on HTTP request parameters.
- *
- * Authentication by X509Certificate is tried first and then by cookie.
- *
- * @param httpRequest
- * @return a user object or null
- */
- public UserModel authenticate(HttpServletRequest httpRequest) {
- return authenticate(httpRequest, false);
- }
-
- /**
- * Authenticate a user based on HTTP request parameters.
- *
- * Authentication by X509Certificate, servlet container principal, cookie,
- * and BASIC header.
- *
- * @param httpRequest
- * @param requiresCertificate
- * @return a user object or null
- */
- public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
- // try to authenticate by certificate
- boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
- String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
- UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
- if (model != null) {
- // grab real user model and preserve certificate serial number
- UserModel user = getUserModel(model.username);
- X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
- if (user != null) {
- flagWicketSession(AuthenticationType.CERTIFICATE);
- logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
- user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
- return user;
- } else {
- logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
- model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
- }
- }
-
- if (requiresCertificate) {
- // caller requires client certificate authentication (e.g. git servlet)
- return null;
- }
-
- // try to authenticate by servlet container principal
- Principal principal = httpRequest.getUserPrincipal();
- if (principal != null) {
- String username = principal.getName();
- if (StringUtils.isEmpty(username)) {
- UserModel user = getUserModel(username);
- if (user != null) {
- flagWicketSession(AuthenticationType.CONTAINER);
- logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
- user.username, httpRequest.getRemoteAddr()));
- return user;
- } else {
- logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
- principal.getName(), httpRequest.getRemoteAddr()));
- }
- }
- }
-
- // try to authenticate by cookie
- if (allowCookieAuthentication()) {
- UserModel user = authenticate(httpRequest.getCookies());
- if (user != null) {
- flagWicketSession(AuthenticationType.COOKIE);
- logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
- user.username, httpRequest.getRemoteAddr()));
- return user;
- }
- }
-
- // try to authenticate by BASIC
- final String authorization = httpRequest.getHeader("Authorization");
- if (authorization != null && authorization.startsWith("Basic")) {
- // Authorization: Basic base64credentials
- String base64Credentials = authorization.substring("Basic".length()).trim();
- String credentials = new String(Base64.decode(base64Credentials),
- Charset.forName("UTF-8"));
- // credentials = username:password
- final String[] values = credentials.split(":",2);
-
- if (values.length == 2) {
- String username = values[0];
- char[] password = values[1].toCharArray();
- UserModel user = authenticate(username, password);
- if (user != null) {
- flagWicketSession(AuthenticationType.CREDENTIALS);
- logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
- user.username, httpRequest.getRemoteAddr()));
- return user;
- } else {
- logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}",
- username, credentials, httpRequest.getRemoteAddr()));
- }
- }
- }
- return null;
- }
-
- protected void flagWicketSession(AuthenticationType authenticationType) {
- RequestCycle requestCycle = RequestCycle.get();
- if (requestCycle != null) {
- // flag the Wicket session, if this is a Wicket request
- GitBlitWebSession session = GitBlitWebSession.get();
- session.authenticationType = authenticationType;
- }
- }
-
- /**
- * Open a file resource using the Servlet container.
- * @param file to open
- * @return InputStream of the opened file
- * @throws ResourceStreamNotFoundException
- */
- public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
- ContextRelativeResource res = WicketUtils.getResource(file);
- return res.getResourceStream().getInputStream();
- }
-
- /**
- * Sets a cookie for the specified user.
- *
- * @param response
- * @param user
- */
- public void setCookie(WebResponse response, UserModel user) {
- if (userService == null) {
- return;
- }
- if (userService.supportsCookies()) {
- Cookie userCookie;
- if (user == null) {
- // clear cookie for logout
- userCookie = new Cookie(Constants.NAME, "");
- } else {
- // set cookie for login
- 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);
- }
-
- /**
- * Returns the list of all users available to the login service.
- *
- * @see IUserService.getAllUsernames()
- * @return list of all usernames
- */
- public List<String> getAllUsernames() {
- List<String> names = new ArrayList<String>(userService.getAllUsernames());
- return names;
- }
-
- /**
- * Returns the list of all users available to the login service.
- *
- * @see IUserService.getAllUsernames()
- * @return list of all usernames
- */
- public List<UserModel> getAllUsers() {
- List<UserModel> users = userService.getAllUsers();
- return users;
- }
-
- /**
- * Delete the user object with the specified username
- *
- * @see IUserService.deleteUser(String)
- * @param username
- * @return true if successful
- */
- public boolean deleteUser(String username) {
- if (StringUtils.isEmpty(username)) {
- return false;
- }
- return userService.deleteUser(username);
- }
-
- /**
- * Retrieve the user object for the specified username.
- *
- * @see IUserService.getUserModel(String)
- * @param username
- * @return a user object or null
- */
- public UserModel getUserModel(String username) {
- if (StringUtils.isEmpty(username)) {
- return null;
- }
- UserModel user = userService.getUserModel(username);
- return user;
- }
-
- /**
- * Returns the effective list of permissions for this user, taking into account
- * team memberships, ownerships.
- *
- * @param user
- * @return the effective list of permissions for the user
- */
- public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
- if (StringUtils.isEmpty(user.username)) {
- // new user
- return new ArrayList<RegistrantAccessPermission>();
- }
- Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
- set.addAll(user.getRepositoryPermissions());
- // Flag missing repositories
- for (RegistrantAccessPermission permission : set) {
- if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
- RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant);
- if (rm == null) {
- permission.permissionType = PermissionType.MISSING;
- permission.mutable = false;
- continue;
- }
- }
- }
-
- // TODO reconsider ownership as a user property
- // manually specify personal repository ownerships
- for (RepositoryModel rm : repositoryListCache.values()) {
- if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
- RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
- PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
- // user may be owner of a repository to which they've inherited
- // a team permission, replace any existing perm with owner perm
- set.remove(rp);
- set.add(rp);
- }
- }
-
- List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
- Collections.sort(list);
- return list;
- }
-
- /**
- * Returns the list of users and their access permissions for the specified
- * repository including permission source information such as the team or
- * regular expression which sets the permission.
- *
- * @param repository
- * @return a list of RegistrantAccessPermissions
- */
- public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
- List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
- if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
- // no permissions needed, REWIND for everyone!
- return list;
- }
- if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
- // no permissions needed, REWIND for authenticated!
- return list;
- }
- // NAMED users and teams
- for (UserModel user : userService.getAllUsers()) {
- RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
- if (ap.permission.exceeds(AccessPermission.NONE)) {
- list.add(ap);
- }
- }
- return list;
- }
-
- /**
- * Sets the access permissions to the specified repository for the specified users.
- *
- * @param repository
- * @param permissions
- * @return true if the user models have been updated
- */
- public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
- List<UserModel> users = new ArrayList<UserModel>();
- for (RegistrantAccessPermission up : permissions) {
- if (up.mutable) {
- // only set editable defined permissions
- UserModel user = userService.getUserModel(up.registrant);
- user.setRepositoryPermission(repository.name, up.permission);
- users.add(user);
- }
- }
- return userService.updateUserModels(users);
- }
-
- /**
- * Returns the list of all users who have an explicit access permission
- * for the specified repository.
- *
- * @see IUserService.getUsernamesForRepositoryRole(String)
- * @param repository
- * @return list of all usernames that have an access permission for the repository
- */
- public List<String> getRepositoryUsers(RepositoryModel repository) {
- return userService.getUsernamesForRepositoryRole(repository.name);
- }
-
- /**
- * Sets the list of all uses who are allowed to bypass the access
- * restriction placed on the specified repository.
- *
- * @see IUserService.setUsernamesForRepositoryRole(String, List<String>)
- * @param repository
- * @param usernames
- * @return true if successful
- */
- @Deprecated
- public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
- // rejects all changes since 1.2.0 because this would elevate
- // all discrete access permissions to RW+
- return false;
- }
-
- /**
- * Adds/updates a complete user object keyed by username. This method allows
- * for renaming a user.
- *
- * @see IUserService.updateUserModel(String, UserModel)
- * @param username
- * @param user
- * @param isCreate
- * @throws GitBlitException
- */
- 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));
- }
-
- // rename repositories and owner fields for all repositories
- for (RepositoryModel model : getRepositoryModels(user)) {
- if (model.isUsersPersonalRepository(username)) {
- // personal repository
- model.addOwner(user.username);
- String oldRepositoryName = model.name;
- model.name = "~" + user.username + model.name.substring(model.projectPath.length());
- model.projectPath = "~" + user.username;
- updateRepositoryModel(oldRepositoryName, model, false);
- } else if (model.isOwner(username)) {
- // common/shared repo
- model.addOwner(user.username);
- updateRepositoryModel(model.name, model, false);
- }
- }
- }
- if (!userService.updateUserModel(username, user)) {
- throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
- }
- }
-
- /**
- * Returns the list of available teams that a user or repository may be
- * assigned to.
- *
- * @return the list of teams
- */
- public List<String> getAllTeamnames() {
- List<String> teams = new ArrayList<String>(userService.getAllTeamNames());
- return teams;
- }
-
- /**
- * Returns the list of available teams that a user or repository may be
- * assigned to.
- *
- * @return the list of teams
- */
- public List<TeamModel> getAllTeams() {
- List<TeamModel> teams = userService.getAllTeams();
- return teams;
- }
-
- /**
- * Returns the TeamModel object for the specified name.
- *
- * @param teamname
- * @return a TeamModel object or null
- */
- public TeamModel getTeamModel(String teamname) {
- return userService.getTeamModel(teamname);
- }
-
- /**
- * Returns the list of teams and their access permissions for the specified
- * repository including the source of the permission such as the admin flag
- * or a regular expression.
- *
- * @param repository
- * @return a list of RegistrantAccessPermissions
- */
- public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
- List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
- for (TeamModel team : userService.getAllTeams()) {
- RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
- if (ap.permission.exceeds(AccessPermission.NONE)) {
- list.add(ap);
- }
- }
- Collections.sort(list);
- return list;
- }
-
- /**
- * Sets the access permissions to the specified repository for the specified teams.
- *
- * @param repository
- * @param permissions
- * @return true if the team models have been updated
- */
- public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
- List<TeamModel> teams = new ArrayList<TeamModel>();
- for (RegistrantAccessPermission tp : permissions) {
- if (tp.mutable) {
- // only set explicitly defined access permissions
- TeamModel team = userService.getTeamModel(tp.registrant);
- team.setRepositoryPermission(repository.name, tp.permission);
- teams.add(team);
- }
- }
- return userService.updateTeamModels(teams);
- }
-
- /**
- * Returns the list of all teams who have an explicit access permission for
- * the specified repository.
- *
- * @see IUserService.getTeamnamesForRepositoryRole(String)
- * @param repository
- * @return list of all teamnames with explicit access permissions to the repository
- */
- public List<String> getRepositoryTeams(RepositoryModel repository) {
- return userService.getTeamnamesForRepositoryRole(repository.name);
- }
-
- /**
- * Sets the list of all uses who are allowed to bypass the access
- * restriction placed on the specified repository.
- *
- * @see IUserService.setTeamnamesForRepositoryRole(String, List<String>)
- * @param repository
- * @param teamnames
- * @return true if successful
- */
- @Deprecated
- public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
- // rejects all changes since 1.2.0 because this would elevate
- // all discrete access permissions to RW+
- return false;
- }
-
- /**
- * Updates the TeamModel object for the specified name.
- *
- * @param teamname
- * @param team
- * @param isCreate
- */
- public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
- throws GitBlitException {
- if (!teamname.equalsIgnoreCase(team.name)) {
- if (userService.getTeamModel(team.name) != null) {
- throw new GitBlitException(MessageFormat.format(
- "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
- team.name));
- }
- }
- if (!userService.updateTeamModel(teamname, team)) {
- throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
- }
- }
-
- /**
- * Delete the team object with the specified teamname
- *
- * @see IUserService.deleteTeam(String)
- * @param teamname
- * @return true if successful
- */
- 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 model
- */
- private void addToCachedRepositoryList(RepositoryModel model) {
- if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
- repositoryListCache.put(model.name.toLowerCase(), 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);
- origin.addFork(model.name);
- }
- }
- }
- }
-
- /**
- * Removes the repository from the list of cached repositories.
- *
- * @param name
- * @return the model being removed
- */
- private RepositoryModel removeFromCachedRepositoryList(String name) {
- if (StringUtils.isEmpty(name)) {
- return null;
- }
- return repositoryListCache.remove(name.toLowerCase());
- }
-
- /**
- * Clears all the cached metadata for the specified repository.
- *
- * @param 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;
- }
-
- /**
- * Returns the list of all repositories available to Gitblit. This method
- * does not consider user access permissions.
- *
- * @return list of all repositories
- */
- public List<String> getRepositoryList() {
- 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)) {
+ Collection<GitClientApplication> links = gson.fromJson(reader, type);
+ return links;
+ } catch (JsonIOException e) {
+ logger.error("Error deserializing client applications!", e);
+ } catch (JsonSyntaxException e) {
+ logger.error("Error deserializing client applications!", e);
+ }
+ return null;
+ }
+
+ /**
+ * Set the user service. The user service authenticates all users and is
+ * responsible for managing user permissions.
+ *
+ * @param userService
+ */
+ public void setUserService(IUserService userService) {
+ logger.info("Setting up user service " + userService.toString());
+ this.userService = userService;
+ this.userService.setup(settings);
+ }
+
+ public boolean supportsAddUser() {
+ return supportsCredentialChanges(new UserModel(""));
+ }
+
+ /**
+ * Returns true if the user's credentials can be changed.
+ *
+ * @param user
+ * @return true if the user service supports credential changes
+ */
+ public boolean supportsCredentialChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsCredentialChanges();
+ }
+
+ /**
+ * Returns true if the user's display name can be changed.
+ *
+ * @param user
+ * @return true if the user service supports display name changes
+ */
+ public boolean supportsDisplayNameChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsDisplayNameChanges();
+ }
+
+ /**
+ * Returns true if the user's email address can be changed.
+ *
+ * @param user
+ * @return true if the user service supports email address changes
+ */
+ public boolean supportsEmailAddressChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsEmailAddressChanges();
+ }
+
+ /**
+ * Returns true if the user's team memberships can be changed.
+ *
+ * @param user
+ * @return true if the user service supports team membership changes
+ */
+ public boolean supportsTeamMembershipChanges(UserModel user) {
+ return (user != null && user.isLocalAccount()) || userService.supportsTeamMembershipChanges();
+ }
+
+ /**
+ * Authenticate a user based on a username and password.
+ *
+ * @see IUserService.authenticate(String, char[])
+ * @param username
+ * @param password
+ * @return a user object or null
+ */
+ public UserModel authenticate(String username, char[] password) {
+ if (StringUtils.isEmpty(username)) {
+ // can not authenticate empty username
+ return null;
+ }
+ String pw = new String(password);
+ if (StringUtils.isEmpty(pw)) {
+ // can not authenticate empty password
+ return null;
+ }
+
+ // check to see if this is the federation user
+ if (canFederate()) {
+ if (username.equalsIgnoreCase(Constants.FEDERATION_USER)) {
+ List<String> tokens = getFederationTokens();
+ if (tokens.contains(pw)) {
+ // the federation user is an administrator
+ UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
+ federationUser.canAdmin = true;
+ return federationUser;
+ }
+ }
+ }
+
+ // delegate authentication to the user service
+ if (userService == null) {
+ return null;
+ }
+ return userService.authenticate(username, password);
+ }
+
+ /**
+ * Authenticate a user based on their cookie.
+ *
+ * @param cookies
+ * @return a user object or null
+ */
+ protected UserModel authenticate(Cookie[] cookies) {
+ if (userService == null) {
+ return null;
+ }
+ if (userService.supportsCookies()) {
+ if (cookies != null && cookies.length > 0) {
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equals(Constants.NAME)) {
+ String value = cookie.getValue();
+ return userService.authenticate(value.toCharArray());
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Authenticate a user based on HTTP request parameters.
+ *
+ * Authentication by X509Certificate is tried first and then by cookie.
+ *
+ * @param httpRequest
+ * @return a user object or null
+ */
+ public UserModel authenticate(HttpServletRequest httpRequest) {
+ return authenticate(httpRequest, false);
+ }
+
+ /**
+ * Authenticate a user based on HTTP request parameters.
+ *
+ * Authentication by X509Certificate, servlet container principal, cookie,
+ * and BASIC header.
+ *
+ * @param httpRequest
+ * @param requiresCertificate
+ * @return a user object or null
+ */
+ public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
+ // try to authenticate by certificate
+ boolean checkValidity = settings.getBoolean(Keys.git.enforceCertificateValidity, true);
+ String [] oids = getStrings(Keys.git.certificateUsernameOIDs).toArray(new String[0]);
+ UserModel model = HttpUtils.getUserModelFromCertificate(httpRequest, checkValidity, oids);
+ if (model != null) {
+ // grab real user model and preserve certificate serial number
+ UserModel user = getUserModel(model.username);
+ X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
+ if (user != null) {
+ flagWicketSession(AuthenticationType.CERTIFICATE);
+ logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
+ user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
+ return user;
+ } else {
+ logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted client certificate ({1}) authentication from {2}",
+ model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
+ }
+ }
+
+ if (requiresCertificate) {
+ // caller requires client certificate authentication (e.g. git servlet)
+ return null;
+ }
+
+ // try to authenticate by servlet container principal
+ Principal principal = httpRequest.getUserPrincipal();
+ if (principal != null) {
+ String username = principal.getName();
+ if (StringUtils.isEmpty(username)) {
+ UserModel user = getUserModel(username);
+ if (user != null) {
+ flagWicketSession(AuthenticationType.CONTAINER);
+ logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
+ user.username, httpRequest.getRemoteAddr()));
+ return user;
+ } else {
+ logger.warn(MessageFormat.format("Failed to find UserModel for {0}, attempted servlet container authentication from {1}",
+ principal.getName(), httpRequest.getRemoteAddr()));
+ }
+ }
+ }
+
+ // try to authenticate by cookie
+ if (allowCookieAuthentication()) {
+ UserModel user = authenticate(httpRequest.getCookies());
+ if (user != null) {
+ flagWicketSession(AuthenticationType.COOKIE);
+ logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
+ user.username, httpRequest.getRemoteAddr()));
+ return user;
+ }
+ }
+
+ // try to authenticate by BASIC
+ final String authorization = httpRequest.getHeader("Authorization");
+ if (authorization != null && authorization.startsWith("Basic")) {
+ // Authorization: Basic base64credentials
+ String base64Credentials = authorization.substring("Basic".length()).trim();
+ String credentials = new String(Base64.decode(base64Credentials),
+ Charset.forName("UTF-8"));
+ // credentials = username:password
+ final String[] values = credentials.split(":",2);
+
+ if (values.length == 2) {
+ String username = values[0];
+ char[] password = values[1].toCharArray();
+ UserModel user = authenticate(username, password);
+ if (user != null) {
+ flagWicketSession(AuthenticationType.CREDENTIALS);
+ logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
+ user.username, httpRequest.getRemoteAddr()));
+ return user;
+ } else {
+ logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials ({1}) from {2}",
+ username, credentials, httpRequest.getRemoteAddr()));
+ }
+ }
+ }
+ return null;
+ }
+
+ protected void flagWicketSession(AuthenticationType authenticationType) {
+ RequestCycle requestCycle = RequestCycle.get();
+ if (requestCycle != null) {
+ // flag the Wicket session, if this is a Wicket request
+ GitBlitWebSession session = GitBlitWebSession.get();
+ session.authenticationType = authenticationType;
+ }
+ }
+
+ /**
+ * Open a file resource using the Servlet container.
+ * @param file to open
+ * @return InputStream of the opened file
+ * @throws ResourceStreamNotFoundException
+ */
+ public InputStream getResourceAsStream(String file) throws ResourceStreamNotFoundException {
+ ContextRelativeResource res = WicketUtils.getResource(file);
+ return res.getResourceStream().getInputStream();
+ }
+
+ /**
+ * Sets a cookie for the specified user.
+ *
+ * @param response
+ * @param user
+ */
+ public void setCookie(WebResponse response, UserModel user) {
+ if (userService == null) {
+ return;
+ }
+ if (userService.supportsCookies()) {
+ Cookie userCookie;
+ if (user == null) {
+ // clear cookie for logout
+ userCookie = new Cookie(Constants.NAME, "");
+ } else {
+ // set cookie for login
+ 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);
+ }
+
+ /**
+ * Returns the list of all users available to the login service.
+ *
+ * @see IUserService.getAllUsernames()
+ * @return list of all usernames
+ */
+ public List<String> getAllUsernames() {
+ List<String> names = new ArrayList<String>(userService.getAllUsernames());
+ return names;
+ }
+
+ /**
+ * Returns the list of all users available to the login service.
+ *
+ * @see IUserService.getAllUsernames()
+ * @return list of all usernames
+ */
+ public List<UserModel> getAllUsers() {
+ List<UserModel> users = userService.getAllUsers();
+ return users;
+ }
+
+ /**
+ * Delete the user object with the specified username
+ *
+ * @see IUserService.deleteUser(String)
+ * @param username
+ * @return true if successful
+ */
+ public boolean deleteUser(String username) {
+ if (StringUtils.isEmpty(username)) {
+ return false;
+ }
+ return userService.deleteUser(username);
+ }
+
+ /**
+ * Retrieve the user object for the specified username.
+ *
+ * @see IUserService.getUserModel(String)
+ * @param username
+ * @return a user object or null
+ */
+ public UserModel getUserModel(String username) {
+ if (StringUtils.isEmpty(username)) {
+ return null;
+ }
+ UserModel user = userService.getUserModel(username);
+ return user;
+ }
+
+ /**
+ * Returns the effective list of permissions for this user, taking into account
+ * team memberships, ownerships.
+ *
+ * @param user
+ * @return the effective list of permissions for the user
+ */
+ public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+ if (StringUtils.isEmpty(user.username)) {
+ // new user
+ return new ArrayList<RegistrantAccessPermission>();
+ }
+ Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
+ set.addAll(user.getRepositoryPermissions());
+ // Flag missing repositories
+ for (RegistrantAccessPermission permission : set) {
+ if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+ RepositoryModel rm = GitBlit.self().getRepositoryModel(permission.registrant);
+ if (rm == null) {
+ permission.permissionType = PermissionType.MISSING;
+ permission.mutable = false;
+ continue;
+ }
+ }
+ }
+
+ // TODO reconsider ownership as a user property
+ // manually specify personal repository ownerships
+ for (RepositoryModel rm : repositoryListCache.values()) {
+ if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+ RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
+ PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
+ // user may be owner of a repository to which they've inherited
+ // a team permission, replace any existing perm with owner perm
+ set.remove(rp);
+ set.add(rp);
+ }
+ }
+
+ List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * Returns the list of users and their access permissions for the specified
+ * repository including permission source information such as the team or
+ * regular expression which sets the permission.
+ *
+ * @param repository
+ * @return a list of RegistrantAccessPermissions
+ */
+ public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
+ List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+ if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+ // no permissions needed, REWIND for everyone!
+ return list;
+ }
+ if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
+ // no permissions needed, REWIND for authenticated!
+ return list;
+ }
+ // NAMED users and teams
+ for (UserModel user : userService.getAllUsers()) {
+ RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
+ if (ap.permission.exceeds(AccessPermission.NONE)) {
+ list.add(ap);
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Sets the access permissions to the specified repository for the specified users.
+ *
+ * @param repository
+ * @param permissions
+ * @return true if the user models have been updated
+ */
+ public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
+ List<UserModel> users = new ArrayList<UserModel>();
+ for (RegistrantAccessPermission up : permissions) {
+ if (up.mutable) {
+ // only set editable defined permissions
+ UserModel user = userService.getUserModel(up.registrant);
+ user.setRepositoryPermission(repository.name, up.permission);
+ users.add(user);
+ }
+ }
+ return userService.updateUserModels(users);
+ }
+
+ /**
+ * Returns the list of all users who have an explicit access permission
+ * for the specified repository.
+ *
+ * @see IUserService.getUsernamesForRepositoryRole(String)
+ * @param repository
+ * @return list of all usernames that have an access permission for the repository
+ */
+ public List<String> getRepositoryUsers(RepositoryModel repository) {
+ return userService.getUsernamesForRepositoryRole(repository.name);
+ }
+
+ /**
+ * Sets the list of all uses who are allowed to bypass the access
+ * restriction placed on the specified repository.
+ *
+ * @see IUserService.setUsernamesForRepositoryRole(String, List<String>)
+ * @param repository
+ * @param usernames
+ * @return true if successful
+ */
+ @Deprecated
+ public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
+ // rejects all changes since 1.2.0 because this would elevate
+ // all discrete access permissions to RW+
+ return false;
+ }
+
+ /**
+ * Adds/updates a complete user object keyed by username. This method allows
+ * for renaming a user.
+ *
+ * @see IUserService.updateUserModel(String, UserModel)
+ * @param username
+ * @param user
+ * @param isCreate
+ * @throws GitBlitException
+ */
+ 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));
+ }
+
+ // rename repositories and owner fields for all repositories
+ for (RepositoryModel model : getRepositoryModels(user)) {
+ if (model.isUsersPersonalRepository(username)) {
+ // personal repository
+ model.addOwner(user.username);
+ String oldRepositoryName = model.name;
+ model.name = "~" + user.username + model.name.substring(model.projectPath.length());
+ model.projectPath = "~" + user.username;
+ updateRepositoryModel(oldRepositoryName, model, false);
+ } else if (model.isOwner(username)) {
+ // common/shared repo
+ model.addOwner(user.username);
+ updateRepositoryModel(model.name, model, false);
+ }
+ }
+ }
+ if (!userService.updateUserModel(username, user)) {
+ throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
+ }
+ }
+
+ /**
+ * Returns the list of available teams that a user or repository may be
+ * assigned to.
+ *
+ * @return the list of teams
+ */
+ public List<String> getAllTeamnames() {
+ List<String> teams = new ArrayList<String>(userService.getAllTeamNames());
+ return teams;
+ }
+
+ /**
+ * Returns the list of available teams that a user or repository may be
+ * assigned to.
+ *
+ * @return the list of teams
+ */
+ public List<TeamModel> getAllTeams() {
+ List<TeamModel> teams = userService.getAllTeams();
+ return teams;
+ }
+
+ /**
+ * Returns the TeamModel object for the specified name.
+ *
+ * @param teamname
+ * @return a TeamModel object or null
+ */
+ public TeamModel getTeamModel(String teamname) {
+ return userService.getTeamModel(teamname);
+ }
+
+ /**
+ * Returns the list of teams and their access permissions for the specified
+ * repository including the source of the permission such as the admin flag
+ * or a regular expression.
+ *
+ * @param repository
+ * @return a list of RegistrantAccessPermissions
+ */
+ public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
+ List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+ for (TeamModel team : userService.getAllTeams()) {
+ RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
+ if (ap.permission.exceeds(AccessPermission.NONE)) {
+ list.add(ap);
+ }
+ }
+ Collections.sort(list);
+ return list;
+ }
+
+ /**
+ * Sets the access permissions to the specified repository for the specified teams.
+ *
+ * @param repository
+ * @param permissions
+ * @return true if the team models have been updated
+ */
+ public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
+ List<TeamModel> teams = new ArrayList<TeamModel>();
+ for (RegistrantAccessPermission tp : permissions) {
+ if (tp.mutable) {
+ // only set explicitly defined access permissions
+ TeamModel team = userService.getTeamModel(tp.registrant);
+ team.setRepositoryPermission(repository.name, tp.permission);
+ teams.add(team);
+ }
+ }
+ return userService.updateTeamModels(teams);
+ }
+
+ /**
+ * Returns the list of all teams who have an explicit access permission for
+ * the specified repository.
+ *
+ * @see IUserService.getTeamnamesForRepositoryRole(String)
+ * @param repository
+ * @return list of all teamnames with explicit access permissions to the repository
+ */
+ public List<String> getRepositoryTeams(RepositoryModel repository) {
+ return userService.getTeamnamesForRepositoryRole(repository.name);
+ }
+
+ /**
+ * Sets the list of all uses who are allowed to bypass the access
+ * restriction placed on the specified repository.
+ *
+ * @see IUserService.setTeamnamesForRepositoryRole(String, List<String>)
+ * @param repository
+ * @param teamnames
+ * @return true if successful
+ */
+ @Deprecated
+ public boolean setRepositoryTeams(RepositoryModel repository, List<String> repositoryTeams) {
+ // rejects all changes since 1.2.0 because this would elevate
+ // all discrete access permissions to RW+
+ return false;
+ }
+
+ /**
+ * Updates the TeamModel object for the specified name.
+ *
+ * @param teamname
+ * @param team
+ * @param isCreate
+ */
+ public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
+ throws GitBlitException {
+ if (!teamname.equalsIgnoreCase(team.name)) {
+ if (userService.getTeamModel(team.name) != null) {
+ throw new GitBlitException(MessageFormat.format(
+ "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
+ team.name));
+ }
+ }
+ if (!userService.updateTeamModel(teamname, team)) {
+ throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
+ }
+ }
+
+ /**
+ * Delete the team object with the specified teamname
+ *
+ * @see IUserService.deleteTeam(String)
+ * @param teamname
+ * @return true if successful
+ */
+ 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 model
+ */
+ private void addToCachedRepositoryList(RepositoryModel model) {
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ repositoryListCache.put(model.name.toLowerCase(), 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);
+ origin.addFork(model.name);
+ }
+ }
+ }
+ }
+
+ /**
+ * Removes the repository from the list of cached repositories.
+ *
+ * @param name
+ * @return the model being removed
+ */
+ private RepositoryModel removeFromCachedRepositoryList(String name) {
+ if (StringUtils.isEmpty(name)) {
+ return null;
+ }
+ return repositoryListCache.remove(name.toLowerCase());
+ }
+
+ /**
+ * Clears all the cached metadata for the specified repository.
+ *
+ * @param 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;
+ }
+
+ /**
+ * Returns the list of all repositories available to Gitblit. This method
+ * does not consider user access permissions.
+ *
+ * @return list of all repositories
+ */
+ public List<String> getRepositoryList() {
+ 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)) {
ByteFormat byteFormat = new ByteFormat();
- msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
- for (String repository : repositories) {
- RepositoryModel model = getRepositoryModel(repository);
- if (!model.skipSizeCalculation) {
+ msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
+ for (String repository : repositories) {
+ RepositoryModel model = getRepositoryModel(repository);
+ if (!model.skipSizeCalculation) {
model.size = byteFormat.format(calculateSize(model));
- }
- }
- } else {
- // update cache
- for (String repository : repositories) {
- getRepositoryModel(repository);
- }
- }
-
- // rebuild fork networks
- for (RepositoryModel model : repositoryListCache.values()) {
- if (!StringUtils.isEmpty(model.originRepository)) {
- if (repositoryListCache.containsKey(model.originRepository)) {
- RepositoryModel origin = repositoryListCache.get(model.originRepository);
- origin.addFork(model.name);
- }
- }
- }
-
- 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;
- }
-
- /**
- * Returns the JGit repository for the specified name.
- *
- * @param repositoryName
- * @return repository or null
- */
- public Repository getRepository(String repositoryName) {
- return getRepository(repositoryName, true);
- }
-
- /**
- * Returns the JGit repository for the specified name.
- *
- * @param repositoryName
- * @param logError
- * @return repository or null
- */
- public Repository getRepository(String repositoryName, boolean logError) {
- if (isCollectingGarbage(repositoryName)) {
- logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName));
- return null;
- }
-
- File dir = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED);
- if (dir == null)
- return null;
-
- Repository r = null;
- try {
- FileKey key = FileKey.exact(dir, FS.DETECTED);
- r = RepositoryCache.open(key, true);
- } catch (IOException e) {
- if (logError) {
- logger.error("GitBlit.getRepository(String) failed to find "
- + new File(repositoriesFolder, repositoryName).getAbsolutePath());
- }
- }
- return r;
- }
-
- /**
- * Returns the list of repository models that are accessible to the user.
- *
- * @param user
- * @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) {
- RepositoryModel model = getRepositoryModel(user, repo);
- if (model != null) {
- if (!model.hasCommits) {
- // only add empty repositories that user can push to
- if (UserModel.ANONYMOUS.canPush(model)
- || user != null && user.canPush(model)) {
- repositories.add(model);
- }
- } else {
- 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;
- 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;
- }
-
- /**
- * Returns a repository model if the repository exists and the user may
- * access the repository.
- *
- * @param user
- * @param repositoryName
- * @return repository model or null
- */
- public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
- RepositoryModel model = getRepositoryModel(repositoryName);
- if (model == null) {
- return null;
- }
- if (user == null) {
- user = UserModel.ANONYMOUS;
- }
- if (user.canView(model)) {
- return model;
- }
- return null;
- }
-
- /**
- * Returns the repository model for the specified repository. This method
- * does not consider user access permissions.
- *
- * @param repositoryName
- * @return repository model or null
- */
- public RepositoryModel getRepositoryModel(String repositoryName) {
- if (!repositoryListCache.containsKey(repositoryName)) {
- RepositoryModel model = loadRepositoryModel(repositoryName);
- if (model == null) {
- return null;
- }
- addToCachedRepositoryList(model);
- return model;
- }
-
- // cached model
- RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
-
- if (gcExecutor.isCollectingGarbage(model.name)) {
- // Gitblit is busy collecting garbage, use our cached model
- RepositoryModel rm = DeepCopier.copy(model);
- rm.isCollectingGarbage = true;
- return rm;
- }
-
- // check for updates
- Repository r = getRepository(model.name);
- 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.debug(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
- model = loadRepositoryModel(model.name);
- removeFromCachedRepositoryList(model.name);
- addToCachedRepositoryList(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);
+ }
+ }
+ } else {
+ // update cache
+ for (String repository : repositories) {
+ getRepositoryModel(repository);
+ }
+ }
+
+ // rebuild fork networks
+ for (RepositoryModel model : repositoryListCache.values()) {
+ if (!StringUtils.isEmpty(model.originRepository)) {
+ if (repositoryListCache.containsKey(model.originRepository)) {
+ RepositoryModel origin = repositoryListCache.get(model.originRepository);
+ origin.addFork(model.name);
+ }
+ }
+ }
+
+ 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;
+ }
+
+ /**
+ * Returns the JGit repository for the specified name.
+ *
+ * @param repositoryName
+ * @return repository or null
+ */
+ public Repository getRepository(String repositoryName) {
+ return getRepository(repositoryName, true);
+ }
+
+ /**
+ * Returns the JGit repository for the specified name.
+ *
+ * @param repositoryName
+ * @param logError
+ * @return repository or null
+ */
+ public Repository getRepository(String repositoryName, boolean logError) {
+ if (isCollectingGarbage(repositoryName)) {
+ logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName));
+ return null;
+ }
+
+ File dir = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED);
+ if (dir == null)
+ return null;
+
+ Repository r = null;
+ try {
+ FileKey key = FileKey.exact(dir, FS.DETECTED);
+ r = RepositoryCache.open(key, true);
+ } catch (IOException e) {
+ if (logError) {
+ logger.error("GitBlit.getRepository(String) failed to find "
+ + new File(repositoriesFolder, repositoryName).getAbsolutePath());
+ }
+ }
+ return r;
+ }
+
+ /**
+ * Returns the list of repository models that are accessible to the user.
+ *
+ * @param user
+ * @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) {
+ RepositoryModel model = getRepositoryModel(user, repo);
+ if (model != null) {
+ if (!model.hasCommits) {
+ // only add empty repositories that user can push to
+ if (UserModel.ANONYMOUS.canPush(model)
+ || user != null && user.canPush(model)) {
+ repositories.add(model);
+ }
+ } else {
+ 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;
+ 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;
+ }
+
+ /**
+ * Returns a repository model if the repository exists and the user may
+ * access the repository.
+ *
+ * @param user
+ * @param repositoryName
+ * @return repository model or null
+ */
+ public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
+ RepositoryModel model = getRepositoryModel(repositoryName);
+ if (model == null) {
+ return null;
+ }
+ if (user == null) {
+ user = UserModel.ANONYMOUS;
+ }
+ if (user.canView(model)) {
+ return model;
+ }
+ return null;
+ }
+
+ /**
+ * Returns the repository model for the specified repository. This method
+ * does not consider user access permissions.
+ *
+ * @param repositoryName
+ * @return repository model or null
+ */
+ public RepositoryModel getRepositoryModel(String repositoryName) {
+ if (!repositoryListCache.containsKey(repositoryName)) {
+ RepositoryModel model = loadRepositoryModel(repositoryName);
+ if (model == null) {
+ return null;
+ }
+ addToCachedRepositoryList(model);
+ return model;
+ }
+
+ // cached model
+ RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
+
+ if (gcExecutor.isCollectingGarbage(model.name)) {
+ // Gitblit is busy collecting garbage, use our cached model
+ RepositoryModel rm = DeepCopier.copy(model);
+ rm.isCollectingGarbage = true;
+ return rm;
+ }
+
+ // check for updates
+ Repository r = getRepository(model.name);
+ 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.debug(MessageFormat.format("Config for \"{0}\" has changed. Reloading model and updating cache.", repositoryName));
+ model = loadRepositoryModel(model.name);
+ removeFromCachedRepositoryList(model.name);
+ addToCachedRepositoryList(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);
if (!model.skipSizeCalculation) {
ByteFormat byteFormat = new ByteFormat();
model.size = byteFormat.format(calculateSize(model));
}
- }
- 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 (projectCache.isEmpty() || 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");
-
- // project markdown
- File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd");
- if (pmkd.exists()) {
- Date lm = new Date(pmkd.lastModified());
- if (!projectMarkdownCache.hasCurrent(name, lm)) {
- String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n");
- projectMarkdownCache.updateObject(name, lm, mkd);
- }
- project.projectMarkdown = projectMarkdownCache.getObject(name);
- }
-
- // project repositories markdown
- File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd");
- if (rmkd.exists()) {
- Date lm = new Date(rmkd.lastModified());
- if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) {
- String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n");
- projectRepositoriesMarkdownCache.updateObject(name, lm, mkd);
- }
- project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name);
- }
-
- configs.put(name.toLowerCase(), project);
- }
- projectCache.clear();
- projectCache.putAll(configs);
- }
- return projectCache;
- }
-
- /**
- * Returns a list of project models for the user.
- *
- * @param user
- * @param includeUsers
- * @return list of projects that are accessible to the user
- */
- public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
- 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;
- if (includeUsers) {
- // all projects
- projects = new ArrayList<ProjectModel>(map.values());
- Collections.sort(projects);
- projects.remove(map.get(""));
- projects.add(0, map.get(""));
- } else {
- // all non-user projects
- projects = new ArrayList<ProjectModel>();
- ProjectModel root = map.remove("");
- for (ProjectModel model : map.values()) {
- if (!model.isUserProject()) {
- projects.add(model);
- }
- }
- Collections.sort(projects);
- projects.add(0, root);
- }
- 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, true)) {
- 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) {
- project = new ProjectModel(name);
- if (name.length() > 0 && name.charAt(0) == '~') {
- UserModel user = getUserModel(name.substring(1));
- if (user != null) {
- project.title = user.getDisplayName();
- project.description = "personal repositories";
- }
- }
- } else {
- // clone the object
- project = DeepCopier.copy(project);
- }
- if (StringUtils.isEmpty(name)) {
- // get root repositories
- for (String repository : getRepositoryList()) {
- if (repository.indexOf('/') == -1) {
- project.addRepository(repository);
- }
- }
- } else {
- // get repositories in subfolder
- String folder = name.toLowerCase() + "/";
- for (String repository : getRepositoryList()) {
- if (repository.toLowerCase().startsWith(folder)) {
- project.addRepository(repository);
- }
- }
- }
- if (project.repositories.size() == 0) {
- // no repositories == no project
- return null;
- }
- return project;
- }
-
- /**
- * Returns the list of project models that are referenced by the supplied
- * repository model list. This is an alternative method exists to ensure
- * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
- *
- * @param repositoryModels
- * @param includeUsers
- * @return a list of project models
- */
- public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
- Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
- for (RepositoryModel repository : repositoryModels) {
- if (!includeUsers && repository.isPersonalRepository()) {
- // exclude personal repositories
- continue;
- }
- if (!projects.containsKey(repository.projectPath)) {
- ProjectModel project = getProjectModel(repository.projectPath);
- if (project == null) {
- logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
- repository.projectPath));
- continue;
- }
- projects.put(repository.projectPath, project);
- // clear the repo list in the project because that is the system
- // list, not the user-accessible list and start building the
- // user-accessible list
- project.repositories.clear();
- project.repositories.add(repository.name);
- project.lastChange = repository.lastChange;
- } else {
- // update the user-accessible list
- // this is used for repository count
- ProjectModel project = projects.get(repository.projectPath);
- project.repositories.add(repository.name);
- if (project.lastChange.before(repository.lastChange)) {
- project.lastChange = repository.lastChange;
- }
- }
- }
- return new ArrayList<ProjectModel>(projects.values());
- }
-
- /**
- * 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;
- }
- RepositoryModel model = new RepositoryModel();
- model.isBare = r.isBare();
- File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
- if (model.isBare) {
- model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
- } else {
- model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile());
- }
- if (StringUtils.isEmpty(model.name)) {
- // Repository is NOT located relative to the base folder because it
- // is symlinked. Use the provided repository name.
- model.name = repositoryName;
- }
- model.hasCommits = JGitUtils.hasCommits(r);
- model.lastChange = JGitUtils.getLastChange(r);
- model.projectPath = StringUtils.getFirstPathElement(repositoryName);
-
- StoredConfig config = r.getConfig();
- boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
-
- if (config != null) {
- model.description = getConfig(config, "description", "");
- model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
- model.useTickets = getConfig(config, "useTickets", false);
- model.useDocs = getConfig(config, "useDocs", false);
- model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false);
- model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null);
- model.allowForks = getConfig(config, "allowForks", true);
- model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
- "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null)));
- model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
- "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
- model.verifyCommitter = getConfig(config, "verifyCommitter", false);
- model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin);
- 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(
- Constants.CONFIG_GITBLIT, null, "federationSets")));
- model.isFederated = getConfig(config, "isFederated", false);
- model.gcThreshold = getConfig(config, "gcThreshold", settings.getString(Keys.git.defaultGarbageCollectionThreshold, "500KB"));
- model.gcPeriod = getConfig(config, "gcPeriod", settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7));
- try {
- model.lastGC = new SimpleDateFormat(Constants.ISO8601).parse(getConfig(config, "lastGC", "1970-01-01'T'00:00:00Z"));
- } catch (Exception e) {
- model.lastGC = new Date(0);
- }
- model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0));
- model.origin = config.getString("remote", "origin", "url");
- if (model.origin != null) {
- model.origin = model.origin.replace('\\', '/');
- }
- model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
- Constants.CONFIG_GITBLIT, null, "preReceiveScript")));
- model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
- Constants.CONFIG_GITBLIT, null, "postReceiveScript")));
- model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList(
- 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);
- model.sparkleshareId = JGitUtils.getSparkleshareId(r);
- r.close();
-
- if (model.origin != null && model.origin.startsWith("file://")) {
- // repository was cloned locally... perhaps as a fork
- try {
- File folder = new File(new URI(model.origin));
- String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder);
- if (!StringUtils.isEmpty(originRepo)) {
- // ensure origin still exists
- File repoFolder = new File(getRepositoriesFolder(), originRepo);
- if (repoFolder.exists()) {
- model.originRepository = originRepo.toLowerCase();
- }
- }
- } catch (URISyntaxException e) {
- logger.error("Failed to determine fork for " + model, e);
- }
- }
- return model;
- }
-
- /**
- * Determines if this server has the requested repository.
- *
- * @param name
- * @return true if the repository exists
- */
- public boolean hasRepository(String repositoryName) {
- return hasRepository(repositoryName, false);
- }
-
- /**
- * Determines if this server has the requested repository.
- *
- * @param name
- * @param caseInsensitive
- * @return true if the repository exists
- */
- public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
- 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());
- }
- Repository r = getRepository(repositoryName, false);
- if (r == null) {
- return false;
- }
- r.close();
- return true;
- }
-
- /**
- * Determines if the specified user has a fork of the specified origin
- * repository.
- *
- * @param username
- * @param origin
- * @return true the if the user has a fork
- */
- public boolean hasFork(String username, String origin) {
- return getFork(username, origin) != null;
- }
-
- /**
- * Gets the name of a user's fork of the specified origin
- * repository.
- *
- * @param username
- * @param origin
- * @return the name of the user's fork, null otherwise
- */
- public String getFork(String username, String origin) {
- String userProject = "~" + username.toLowerCase();
- if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
- String userPath = userProject + "/";
-
- // collect all origin nodes in fork network
- Set<String> roots = new HashSet<String>();
- roots.add(origin);
- RepositoryModel originModel = repositoryListCache.get(origin);
- while (originModel != null) {
- if (!ArrayUtils.isEmpty(originModel.forks)) {
- for (String fork : originModel.forks) {
- if (!fork.startsWith(userPath)) {
- roots.add(fork);
- }
- }
- }
-
- if (originModel.originRepository != null) {
- roots.add(originModel.originRepository);
- originModel = repositoryListCache.get(originModel.originRepository);
- } else {
- // break
- originModel = null;
- }
- }
-
- for (String repository : repositoryListCache.keySet()) {
- if (repository.startsWith(userPath)) {
- RepositoryModel model = repositoryListCache.get(repository);
- if (!StringUtils.isEmpty(model.originRepository)) {
- if (roots.contains(model.originRepository)) {
- // user has a fork in this graph
- return model.name;
- }
- }
- }
- }
- } else {
- // not caching
- ProjectModel project = getProjectModel(userProject);
- if (project == null) {
- return null;
- }
- for (String repository : project.repositories) {
- if (repository.startsWith(userProject)) {
- RepositoryModel model = getRepositoryModel(repository);
- if (model.originRepository.equalsIgnoreCase(origin)) {
- // user has a fork
- return model.name;
- }
- }
- }
- }
- // user does not have a fork
- return null;
- }
-
- /**
- * Returns the fork network for a repository by traversing up the fork graph
- * to discover the root and then down through all children of the root node.
- *
- * @param repository
- * @return a ForkModel
- */
- public ForkModel getForkNetwork(String repository) {
- if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
- // find the root, cached
- RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
- while (model.originRepository != null) {
- model = repositoryListCache.get(model.originRepository);
- }
- ForkModel root = getForkModelFromCache(model.name);
- return root;
- } else {
- // find the root, non-cached
- RepositoryModel model = getRepositoryModel(repository.toLowerCase());
- while (model.originRepository != null) {
- model = getRepositoryModel(model.originRepository);
- }
- ForkModel root = getForkModel(model.name);
- return root;
- }
- }
-
- private ForkModel getForkModelFromCache(String repository) {
- RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
- if (model == null) {
- return null;
- }
- ForkModel fork = new ForkModel(model);
- if (!ArrayUtils.isEmpty(model.forks)) {
- for (String aFork : model.forks) {
- ForkModel fm = getForkModelFromCache(aFork);
- if (fm != null) {
- fork.forks.add(fm);
- }
- }
- }
- return fork;
- }
-
- private ForkModel getForkModel(String repository) {
- RepositoryModel model = getRepositoryModel(repository.toLowerCase());
- if (model == null) {
- return null;
- }
- ForkModel fork = new ForkModel(model);
- if (!ArrayUtils.isEmpty(model.forks)) {
- for (String aFork : model.forks) {
- ForkModel fm = getForkModel(aFork);
- if (fm != null) {
- fork.forks.add(fm);
- }
- }
- }
- return fork;
- }
-
- /**
- * 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);
- long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
- repositorySizeCache.updateObject(model.name, model.lastChange, size);
- return size;
- }
-
- /**
- * Ensure that a cached repository is completely closed and its resources
- * are properly released.
- *
- * @param repositoryName
- */
- private void closeRepository(String repositoryName) {
- Repository repository = getRepository(repositoryName);
- if (repository == null) {
- return;
- }
- RepositoryCache.close(repository);
-
- // assume 2 uses in case reflection fails
- int uses = 2;
- try {
- // The FileResolver caches repositories which is very useful
- // for performance until you want to delete a repository.
- // I have to use reflection to call close() the correct
- // number of times to ensure that the object and ref databases
- // are properly closed before I can delete the repository from
- // the filesystem.
- Field useCnt = Repository.class.getDeclaredField("useCnt");
- useCnt.setAccessible(true);
- uses = ((AtomicInteger) useCnt.get(repository)).get();
- } catch (Exception e) {
- logger.warn(MessageFormat
- .format("Failed to reflectively determine use count for repository {0}",
- repositoryName), e);
- }
- if (uses > 0) {
- logger.info(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++) {
- repository.close();
- }
- }
-
- // close any open index writer/searcher in the Lucene executor
- luceneExecutor.close(repositoryName);
- }
-
- /**
- * 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, getTimezone());
- 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
- * @param field
- * @param defaultValue
- * @return field value or defaultValue
- */
- private String getConfig(StoredConfig config, String field, String defaultValue) {
- String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
- if (StringUtils.isEmpty(value)) {
- return defaultValue;
- }
- return value;
- }
-
- /**
- * Returns the gitblit boolean value for the specified key. If key is not
- * set, returns defaultValue.
- *
- * @param config
- * @param field
- * @param defaultValue
- * @return field value or defaultValue
- */
- private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
- return config.getBoolean(Constants.CONFIG_GITBLIT, field, defaultValue);
- }
-
- /**
- * Returns the gitblit string value for the specified key. If key is not
- * set, returns defaultValue.
- *
- * @param config
- * @param field
- * @param defaultValue
- * @return field value or defaultValue
- */
- private int getConfig(StoredConfig config, String field, int defaultValue) {
- String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
- if (StringUtils.isEmpty(value)) {
- return defaultValue;
- }
- try {
- return Integer.parseInt(value);
- } catch (Exception e) {
- }
- return defaultValue;
- }
-
- /**
- * Creates/updates the repository model keyed by reopsitoryName. Saves all
- * repository settings in .git/config. This method allows for renaming
- * repositories and will update user access permissions accordingly.
- *
- * All repositories created by this method are bare and automatically have
- * .git appended to their names, which is the standard convention for bare
- * repositories.
- *
- * @param repositoryName
- * @param repository
- * @param isCreate
- * @throws GitBlitException
- */
- public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
- boolean isCreate) throws GitBlitException {
- if (gcExecutor.isCollectingGarbage(repositoryName)) {
- throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}",
- repositoryName));
- }
- Repository r = null;
- String projectPath = StringUtils.getFirstPathElement(repository.name);
- if (!StringUtils.isEmpty(projectPath)) {
- if (projectPath.equalsIgnoreCase(getString(Keys.web.repositoryRootGroupName, "main"))) {
- // strip leading group name
- repository.name = repository.name.substring(projectPath.length() + 1);
- }
- }
- if (isCreate) {
- // ensure created repository name ends with .git
- if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
- repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
- }
- if (hasRepository(repository.name)) {
- throw new GitBlitException(MessageFormat.format(
- "Can not create repository ''{0}'' because it already exists.",
- repository.name));
- }
- // create repository
- logger.info("create repository " + repository.name);
- r = JGitUtils.createRepository(repositoriesFolder, repository.name);
- } 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);
- if (destFolder.exists()) {
- throw new GitBlitException(
- MessageFormat
- .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
- repositoryName, repository.name));
- }
- File parentFile = destFolder.getParentFile();
- if (!parentFile.exists() && !parentFile.mkdirs()) {
- throw new GitBlitException(MessageFormat.format(
- "Failed to create folder ''{0}''", parentFile.getAbsolutePath()));
- }
- if (!folder.renameTo(destFolder)) {
- throw new GitBlitException(MessageFormat.format(
- "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
- repository.name));
- }
- // rename the roles
- if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
- throw new GitBlitException(MessageFormat.format(
- "Failed to rename repository permissions ''{0}'' to ''{1}''.",
- repositoryName, repository.name));
- }
-
- // rename fork origins in their configs
- if (!ArrayUtils.isEmpty(repository.forks)) {
- for (String fork : repository.forks) {
- Repository rf = getRepository(fork);
- try {
- StoredConfig config = rf.getConfig();
- String origin = config.getString("remote", "origin", "url");
- origin = origin.replace(repositoryName, repository.name);
- config.setString("remote", "origin", "url", origin);
- config.save();
- } catch (Exception e) {
- logger.error("Failed to update repository fork config for " + fork, e);
- }
- rf.close();
- }
- }
-
- // remove this repository from any origin model's fork list
- if (!StringUtils.isEmpty(repository.originRepository)) {
- RepositoryModel origin = repositoryListCache.get(repository.originRepository);
- if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
- origin.forks.remove(repositoryName);
- }
- }
-
- // clear the cache
- clearRepositoryMetadataCache(repositoryName);
- repository.resetDisplayName();
- }
-
- // load repository
- logger.info("edit repository " + repository.name);
- r = getRepository(repository.name);
- }
-
- // update settings
- if (r != null) {
- updateConfiguration(r, repository);
- // only update symbolic head if it changes
- String currentRef = JGitUtils.getHEADRef(r);
- if (!StringUtils.isEmpty(repository.HEAD) && !repository.HEAD.equals(currentRef)) {
- logger.info(MessageFormat.format("Relinking {0} HEAD from {1} to {2}",
- repository.name, currentRef, repository.HEAD));
- if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
- // clear the cache
- 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);
- }
-
- /**
- * Updates the Gitblit configuration for the specified repository.
- *
- * @param r
- * the Git repository
- * @param repository
- * the Gitblit repository model
- */
- public void updateConfiguration(Repository r, RepositoryModel repository) {
- StoredConfig config = r.getConfig();
- config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
- config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
- config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
- config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
- config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags);
- if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) ||
- repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) {
- config.unset(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix");
- } else {
- config.setString(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix", repository.incrementalPushTagPrefix);
- }
- config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
- 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, "verifyCommitter", repository.verifyCommitter);
- 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(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
- config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
- if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) {
- // use default from config
- config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod");
- } else {
- config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
- }
- if (repository.lastGC != null) {
- config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
- }
- if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) {
- // use default from config
- config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits");
- } else {
- config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits);
- }
-
- 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);
- }
- }
- }
-
- 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);
- }
- }
-
- /**
- * Deletes the repository from the file system and removes the repository
- * permission from all repository users.
- *
- * @param model
- * @return true if successful
- */
- public boolean deleteRepositoryModel(RepositoryModel model) {
- return deleteRepository(model.name);
- }
-
- /**
- * Deletes the repository from the file system and removes the repository
- * permission from all repository users.
- *
- * @param repositoryName
- * @return true if successful
- */
- public boolean deleteRepository(String repositoryName) {
- try {
- closeRepository(repositoryName);
- // clear the repository cache
- clearRepositoryMetadataCache(repositoryName);
-
- RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
- if (model != null && !ArrayUtils.isEmpty(model.forks)) {
- resetRepositoryListCache();
- }
-
- 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;
- }
- }
- } catch (Throwable t) {
- logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
- }
- return false;
- }
-
- /**
- * Returns an html version of the commit message with any global or
- * repository-specific regular expression substitution applied.
- *
- * @param repositoryName
- * @param text
- * @return html version of the commit message
- */
- public String processCommitMessage(String repositoryName, String text) {
- String html = StringUtils.breakLinesForHtml(text);
- Map<String, String> map = new HashMap<String, String>();
- // global regex keys
- if (settings.getBoolean(Keys.regex.global, false)) {
- for (String key : settings.getAllKeys(Keys.regex.global)) {
- if (!key.equals(Keys.regex.global)) {
- String subKey = key.substring(key.lastIndexOf('.') + 1);
- map.put(subKey, settings.getString(key, ""));
- }
- }
- }
-
- // repository-specific regex keys
- List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
- + repositoryName.toLowerCase());
- for (String key : keys) {
- String subKey = key.substring(key.lastIndexOf('.') + 1);
- map.put(subKey, settings.getString(key, ""));
- }
-
- for (Entry<String, String> entry : map.entrySet()) {
- String definition = entry.getValue().trim();
- String[] chunks = definition.split("!!!");
- if (chunks.length == 2) {
- html = html.replaceAll(chunks[0], chunks[1]);
- } else {
- logger.warn(entry.getKey()
- + " improperly formatted. Use !!! to separate match from replacement: "
- + definition);
- }
- }
- return html;
- }
-
- /**
- * Returns Gitblit's scheduled executor service for scheduling tasks.
- *
- * @return scheduledExecutor
- */
- public ScheduledExecutorService executor() {
- return scheduledExecutor;
- }
-
- public static boolean canFederate() {
- String passphrase = getString(Keys.federation.passphrase, "");
- return !StringUtils.isEmpty(passphrase);
- }
-
- /**
- * Configures this Gitblit instance to pull any registered federated gitblit
- * instances.
- */
- private void configureFederation() {
- 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 (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) {
- FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
- scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
- }
- }
-
- /**
- * Returns the list of federated gitblit instances that this instance will
- * try to pull.
- *
- * @return list of registered gitblit instances
- */
- public List<FederationModel> getFederationRegistrations() {
- if (federationRegistrations.isEmpty()) {
- federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
- }
- return federationRegistrations;
- }
-
- /**
- * Retrieve the specified federation registration.
- *
- * @param name
- * the name of the registration
- * @return a federation registration
- */
- public FederationModel getFederationRegistration(String url, String name) {
- // check registrations
- for (FederationModel r : getFederationRegistrations()) {
- if (r.name.equals(name) && r.url.equals(url)) {
- return r;
- }
- }
-
- // check the results
- for (FederationModel r : getFederationResultRegistrations()) {
- if (r.name.equals(name) && r.url.equals(url)) {
- return r;
- }
- }
- return null;
- }
-
- /**
- * 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;
- }
-
- /**
- * Returns the specified federation token for this Gitblit instance.
- *
- * @param type
- * @return a federation token
- */
- public String getFederationToken(FederationToken type) {
- 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);
- }
-
- /**
- * Compares the provided token with this Gitblit instance's tokens and
- * determines if the requested permission may be granted to the token.
- *
- * @param req
- * @param token
- * @return true if the request can be executed
- */
- public boolean validateFederationRequest(FederationRequest req, String token) {
- String all = getFederationToken(FederationToken.ALL);
- String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
- String jur = getFederationToken(FederationToken.REPOSITORIES);
- switch (req) {
- case PULL_REPOSITORIES:
- return token.equals(all) || token.equals(unr) || token.equals(jur);
- case PULL_USERS:
- case PULL_TEAMS:
- return token.equals(all) || token.equals(unr);
- case PULL_SETTINGS:
- case PULL_SCRIPTS:
- return token.equals(all);
- default:
- break;
- }
- return false;
- }
-
- /**
- * Acknowledge and cache the status of a remote Gitblit instance.
- *
- * @param identification
- * the identification of the pulling Gitblit instance
- * @param registration
- * the registration from the pulling Gitblit instance
- * @return true if acknowledged
- */
- public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
- // reset the url to the identification of the pulling Gitblit instance
- registration.url = identification;
- String id = identification;
- if (!StringUtils.isEmpty(registration.folder)) {
- id += "-" + registration.folder;
- }
- federationPullResults.put(id, registration);
- return true;
- }
-
- /**
- * Returns the list of registration results.
- *
- * @return the list of registration results
- */
- public List<FederationModel> getFederationResultRegistrations() {
- return new ArrayList<FederationModel>(federationPullResults.values());
- }
-
- /**
- * Submit a federation proposal. The proposal is cached locally and the
- * Gitblit administrator(s) are notified via email.
- *
- * @param proposal
- * the proposal
- * @param gitblitUrl
- * 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
- String json = JsonUtils.toJsonString(proposal);
-
- try {
- // make the proposals folder
- File proposalsFolder = getProposalsFolder();
- proposalsFolder.mkdirs();
-
- // cache json to a file
- File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
- com.gitblit.utils.FileUtils.writeContent(file, json);
- } catch (Exception e) {
- logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
- }
-
- // send an email, if possible
- sendMailToAdministrators("Federation proposal from " + proposal.url,
- "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
- return true;
- }
-
- /**
- * Returns the list of pending federation proposals
- *
- * @return list of federation proposals
- */
- public List<FederationProposal> getPendingFederationProposals() {
- List<FederationProposal> list = new ArrayList<FederationProposal>();
- File folder = getProposalsFolder();
- if (folder.exists()) {
- File[] files = folder.listFiles(new FileFilter() {
- @Override
- public boolean accept(File file) {
- return file.isFile()
- && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
- }
- });
- for (File file : files) {
- String json = com.gitblit.utils.FileUtils.readContent(file, null);
- 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;
- default:
- 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;
- }
-
- /**
- * Returns the proposal identified by the supplied token.
- *
- * @param token
- * @return the specified proposal or null
- */
- public FederationProposal getPendingFederationProposal(String token) {
- List<FederationProposal> list = getPendingFederationProposals();
- for (FederationProposal proposal : list) {
- if (proposal.token.equals(token)) {
- return proposal;
- }
- }
- return null;
- }
-
- /**
- * Deletes a pending federation proposal.
- *
- * @param a
- * proposal
- * @return true if the proposal was deleted
- */
- public boolean deletePendingFederationProposal(FederationProposal proposal) {
- File folder = getProposalsFolder();
- File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
- return file.delete();
- }
-
- /**
- * Returns the list of all Groovy push hook scripts. Script files must have
- * .groovy extension
- *
- * @return list of available hook scripts
- */
- public List<String> getAllScripts() {
- File groovyFolder = getGroovyScriptsFolder();
- File[] files = groovyFolder.listFiles(new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.isFile() && pathname.getName().endsWith(".groovy");
- }
- });
- List<String> scripts = new ArrayList<String>();
- if (files != null) {
- for (File file : files) {
- String script = file.getName().substring(0, file.getName().lastIndexOf('.'));
- scripts.add(script);
- }
- }
- return scripts;
- }
-
- /**
- * Returns the list of pre-receive scripts the repository inherited from the
- * global settings and team affiliations.
- *
- * @param repository
- * if null only the globally specified scripts are returned
- * @return a list of scripts
- */
- public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
- Set<String> scripts = new LinkedHashSet<String>();
- // Globals
- for (String script : getStrings(Keys.groovy.preReceiveScripts)) {
- if (script.endsWith(".groovy")) {
- scripts.add(script.substring(0, script.lastIndexOf('.')));
- } else {
- scripts.add(script);
- }
- }
-
- // Team Scripts
- if (repository != null) {
- for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
- TeamModel team = userService.getTeamModel(teamname);
- scripts.addAll(team.preReceiveScripts);
- }
- }
- return new ArrayList<String>(scripts);
- }
-
- /**
- * Returns the list of all available Groovy pre-receive push hook scripts
- * that are not already inherited by the repository. Script files must have
- * .groovy extension
- *
- * @param repository
- * optional parameter
- * @return list of available hook scripts
- */
- public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
- Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository));
-
- // create list of available scripts by excluding inherited scripts
- List<String> scripts = new ArrayList<String>();
- for (String script : getAllScripts()) {
- if (!inherited.contains(script)) {
- scripts.add(script);
- }
- }
- return scripts;
- }
-
- /**
- * Returns the list of post-receive scripts the repository inherited from
- * the global settings and team affiliations.
- *
- * @param repository
- * if null only the globally specified scripts are returned
- * @return a list of scripts
- */
- public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
- Set<String> scripts = new LinkedHashSet<String>();
- // Global Scripts
- for (String script : getStrings(Keys.groovy.postReceiveScripts)) {
- if (script.endsWith(".groovy")) {
- scripts.add(script.substring(0, script.lastIndexOf('.')));
- } else {
- scripts.add(script);
- }
- }
- // Team Scripts
- if (repository != null) {
- for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
- TeamModel team = userService.getTeamModel(teamname);
- scripts.addAll(team.postReceiveScripts);
- }
- }
- return new ArrayList<String>(scripts);
- }
-
- /**
- * Returns the list of unused Groovy post-receive push hook scripts that are
- * not already inherited by the repository. Script files must have .groovy
- * extension
- *
- * @param repository
- * optional parameter
- * @return list of available hook scripts
- */
- public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
- Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository));
-
- // create list of available scripts by excluding inherited scripts
- List<String> scripts = new ArrayList<String>();
- for (String script : getAllScripts()) {
- if (!inherited.contains(script)) {
- scripts.add(script);
- }
- }
- 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.
- *
- * @param subject
- * @param message
- */
- public void sendMailToAdministrators(String subject, String message) {
- List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
- sendMail(subject, message, toAddresses);
- }
-
- /**
- * Notify users by email of something.
- *
- * @param subject
- * @param message
- * @param toAddresses
- */
- public void sendMail(String subject, String message, Collection<String> toAddresses) {
- this.sendMail(subject, message, toAddresses.toArray(new String[0]));
- }
-
- /**
- * Notify users by email of something.
- *
- * @param subject
- * @param message
- * @param toAddresses
- */
- public void sendMail(String subject, String message, String... toAddresses) {
- if (toAddresses == null || toAddresses.length == 0) {
- logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject));
- return;
- }
- try {
- Message mail = mailExecutor.createMessage(toAddresses);
- if (mail != null) {
- mail.setSubject(subject);
-
- MimeBodyPart messagePart = new MimeBodyPart();
- messagePart.setText(message, "utf-8");
- messagePart.setHeader("Content-Type", "text/plain; charset=\"utf-8\"");
- messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable");
-
- MimeMultipart multiPart = new MimeMultipart();
- multiPart.addBodyPart(messagePart);
- mail.setContent(multiPart);
-
- mailExecutor.queue(mail);
- }
- } catch (MessagingException e) {
- logger.error("Messaging error", e);
- }
- }
-
- /**
- * Notify users by email of something.
- *
- * @param subject
- * @param message
- * @param toAddresses
- */
- public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
- this.sendHtmlMail(subject, message, toAddresses.toArray(new String[0]));
- }
-
- /**
- * Notify users by email of something.
- *
- * @param subject
- * @param message
- * @param toAddresses
- */
- public void sendHtmlMail(String subject, String message, String... toAddresses) {
- if (toAddresses == null || toAddresses.length == 0) {
- logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject));
- return;
- }
- try {
- Message mail = mailExecutor.createMessage(toAddresses);
- if (mail != null) {
- mail.setSubject(subject);
-
- MimeBodyPart messagePart = new MimeBodyPart();
- messagePart.setText(message, "utf-8");
- messagePart.setHeader("Content-Type", "text/html; charset=\"utf-8\"");
- messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable");
-
- MimeMultipart multiPart = new MimeMultipart();
- multiPart.addBodyPart(messagePart);
- mail.setContent(multiPart);
-
- mailExecutor.queue(mail);
- }
- } catch (MessagingException e) {
- logger.error("Messaging error", e);
- }
- }
-
- /**
- * 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
- for (String key : settings.getAllKeys(null)) {
- SettingModel setting = settingsModel.get(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;
- }
-
- /**
- * 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();
- 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
- // models map.
- InputStream is = getClass().getResourceAsStream("/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, File folder, boolean startFederation) {
- this.settings = settings;
- this.baseFolder = folder;
-
- repositoriesFolder = getRepositoriesFolder();
-
- logger.info("Gitblit base folder = " + folder.getAbsolutePath());
- logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
- logger.info("Gitblit settings = " + settings.toString());
-
- // prepare service executors
- mailExecutor = new MailExecutor(settings);
- luceneExecutor = new LuceneExecutor(settings, repositoriesFolder);
- gcExecutor = new GCExecutor(settings);
-
- // 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());
-
- serverStatus = new ServerStatus(isGO());
-
- if (this.userService == null) {
- String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
- IUserService loginService = null;
- try {
- // check to see if this "file" is a login service class
- Class<?> realmClass = Class.forName(realm);
- loginService = (IUserService) realmClass.newInstance();
- } catch (Throwable t) {
- loginService = new GitblitUserService();
- }
- setUserService(loginService);
- }
-
- // load and cache the project metadata
- projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
- getProjectConfigs();
-
- configureMailExecutor();
- configureLuceneIndexing();
- configureGarbageCollector();
- if (startFederation) {
- configureFederation();
- }
- configureJGit();
- configureFanout();
- configureGitDaemon();
-
- ContainerUtils.CVE_2007_0450.test();
- }
-
- protected void configureMailExecutor() {
- 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.");
- }
- }
-
- protected void configureLuceneIndexing() {
- scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES);
- logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
- }
-
- protected void configureGarbageCollector() {
- // schedule gc engine
- if (gcExecutor.isReady()) {
- logger.info("GC executor is scheduled to 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);
- c.set(Calendar.SECOND, 0);
- c.set(Calendar.MILLISECOND, 0);
- Date cd = c.getTime();
- Date now = new Date();
- int delay = 0;
- if (cd.before(now)) {
- c.add(Calendar.DATE, 1);
- cd = c.getTime();
- }
- delay = (int) ((cd.getTime() - now.getTime())/TimeUtils.MIN);
- String when = delay + " mins";
- if (delay > 60) {
- when = MessageFormat.format("{0,number,0.0} hours", ((float)delay)/60f);
- }
- logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when));
- scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60*24, TimeUnit.MINUTES);
- }
- }
-
- protected void configureJGit() {
- // 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 {
- cfg.install();
- 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);
- }
- }
-
- protected void configureFanout() {
- // startup Fanout PubSub service
- if (settings.getInteger(Keys.fanout.port, 0) > 0) {
- String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
- int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
- boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
- int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
-
- if (useNio) {
- if (StringUtils.isEmpty(bindInterface)) {
- fanoutService = new FanoutNioService(port);
- } else {
- fanoutService = new FanoutNioService(bindInterface, port);
- }
- } else {
- if (StringUtils.isEmpty(bindInterface)) {
- fanoutService = new FanoutSocketService(port);
- } else {
- fanoutService = new FanoutSocketService(bindInterface, port);
- }
- }
-
- fanoutService.setConcurrentConnectionLimit(limit);
- fanoutService.setAllowAllChannelAnnouncements(false);
- fanoutService.start();
- }
- }
-
- protected void configureGitDaemon() {
- int port = settings.getInteger(Keys.git.daemonPort, 0);
- String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
- if (port > 0) {
- try {
- gitDaemon = new GitDaemon(bindInterface, port, getRepositoriesFolder());
- gitDaemon.start();
- } catch (IOException e) {
- gitDaemon = null;
- logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e);
- }
- }
- }
-
- protected final Logger getLogger() {
- return logger;
- }
-
- protected final ScheduledExecutorService getScheduledExecutor() {
- return scheduledExecutor;
- }
-
- protected final LuceneExecutor getLuceneExecutor() {
- return luceneExecutor;
- }
-
- private void logTimezone(String type, TimeZone zone) {
- SimpleDateFormat df = new SimpleDateFormat("z Z");
- df.setTimeZone(zone);
- String offset = df.format(new Date());
- logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
- }
-
- /**
- * Configure Gitblit from the web.xml, if no configuration has already been
- * specified.
- *
- * @see ServletContextListener.contextInitialize(ServletContextEvent)
- */
- @Override
- public void contextInitialized(ServletContextEvent contextEvent) {
- servletContext = contextEvent.getServletContext();
- if (settings == null) {
- // Gitblit is running in a servlet container
- ServletContext context = contextEvent.getServletContext();
- WebXmlSettings webxmlSettings = new WebXmlSettings(context);
- String contextRealPath = context.getRealPath("/");
- File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
- String openShift = System.getenv("OPENSHIFT_DATA_DIR");
-
- if (!StringUtils.isEmpty(openShift)) {
- // Gitblit is running in OpenShift/JBoss
- File base = new File(openShift);
- logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
-
- // gitblit.properties setting overrides
- File overrideFile = new File(base, "gitblit.properties");
- webxmlSettings.applyOverrides(overrideFile);
-
- // Copy the included scripts to the configured groovy folder
- String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
- File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
- if (!localScripts.exists()) {
- File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
- if (!warScripts.equals(localScripts)) {
- try {
- com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
- } catch (IOException e) {
- logger.error(MessageFormat.format(
- "Failed to copy included Groovy scripts from {0} to {1}",
- warScripts, localScripts));
- }
- }
- }
-
- // configure context using the web.xml
- configureContext(webxmlSettings, base, true);
- } else {
- // Gitblit is running in a standard servlet container
- logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
-
- String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
-
- if (path.contains(Constants.contextFolder$) && contextFolder == null) {
- // warn about null contextFolder (issue-199)
- logger.error("");
- logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
- Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
- logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
- logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
- logger.error("");
- }
-
- File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
- base.mkdirs();
-
- // try to extract the data folder resource to the baseFolder
- File localSettings = new File(base, "gitblit.properties");
- if (!localSettings.exists()) {
- extractResources(context, "/WEB-INF/data/", base);
- }
-
- // delegate all config to baseFolder/gitblit.properties file
- FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
- configureContext(settings, base, true);
- }
- }
-
- settingsModel = loadSettingModels();
- serverStatus.servletContainer = servletContext.getServerInfo();
- }
-
- protected void extractResources(ServletContext context, String path, File toDir) {
- for (String resource : context.getResourcePaths(path)) {
- // extract the resource to the directory if it does not exist
- File f = new File(toDir, resource.substring(path.length()));
- if (!f.exists()) {
+ }
+ r.close();
+
+ // return a copy of the cached model
+ return DeepCopier.copy(model);
+ }
+
+ /**
+ * Returns the star count of the repository.
+ *
+ * @param repository
+ * @return the star count
+ */
+ public long getStarCount(RepositoryModel repository) {
+ long count = 0;
+ for (UserModel user : getAllUsers()) {
+ if (user.getPreferences().isStarredRepository(repository.name)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+
+ /**
+ * 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 (projectCache.isEmpty() || 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");
+
+ // project markdown
+ File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/project.mkd");
+ if (pmkd.exists()) {
+ Date lm = new Date(pmkd.lastModified());
+ if (!projectMarkdownCache.hasCurrent(name, lm)) {
+ String mkd = com.gitblit.utils.FileUtils.readContent(pmkd, "\n");
+ projectMarkdownCache.updateObject(name, lm, mkd);
+ }
+ project.projectMarkdown = projectMarkdownCache.getObject(name);
+ }
+
+ // project repositories markdown
+ File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : name) + "/repositories.mkd");
+ if (rmkd.exists()) {
+ Date lm = new Date(rmkd.lastModified());
+ if (!projectRepositoriesMarkdownCache.hasCurrent(name, lm)) {
+ String mkd = com.gitblit.utils.FileUtils.readContent(rmkd, "\n");
+ projectRepositoriesMarkdownCache.updateObject(name, lm, mkd);
+ }
+ project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(name);
+ }
+
+ configs.put(name.toLowerCase(), project);
+ }
+ projectCache.clear();
+ projectCache.putAll(configs);
+ }
+ return projectCache;
+ }
+
+ /**
+ * Returns a list of project models for the user.
+ *
+ * @param user
+ * @param includeUsers
+ * @return list of projects that are accessible to the user
+ */
+ public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
+ 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;
+ if (includeUsers) {
+ // all projects
+ projects = new ArrayList<ProjectModel>(map.values());
+ Collections.sort(projects);
+ projects.remove(map.get(""));
+ projects.add(0, map.get(""));
+ } else {
+ // all non-user projects
+ projects = new ArrayList<ProjectModel>();
+ ProjectModel root = map.remove("");
+ for (ProjectModel model : map.values()) {
+ if (!model.isUserProject()) {
+ projects.add(model);
+ }
+ }
+ Collections.sort(projects);
+ projects.add(0, root);
+ }
+ 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, true)) {
+ 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) {
+ project = new ProjectModel(name);
+ if (name.length() > 0 && name.charAt(0) == '~') {
+ UserModel user = getUserModel(name.substring(1));
+ if (user != null) {
+ project.title = user.getDisplayName();
+ project.description = "personal repositories";
+ }
+ }
+ } else {
+ // clone the object
+ project = DeepCopier.copy(project);
+ }
+ if (StringUtils.isEmpty(name)) {
+ // get root repositories
+ for (String repository : getRepositoryList()) {
+ if (repository.indexOf('/') == -1) {
+ project.addRepository(repository);
+ }
+ }
+ } else {
+ // get repositories in subfolder
+ String folder = name.toLowerCase() + "/";
+ for (String repository : getRepositoryList()) {
+ if (repository.toLowerCase().startsWith(folder)) {
+ project.addRepository(repository);
+ }
+ }
+ }
+ if (project.repositories.size() == 0) {
+ // no repositories == no project
+ return null;
+ }
+ return project;
+ }
+
+ /**
+ * Returns the list of project models that are referenced by the supplied
+ * repository model list. This is an alternative method exists to ensure
+ * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
+ *
+ * @param repositoryModels
+ * @param includeUsers
+ * @return a list of project models
+ */
+ public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
+ Map<String, ProjectModel> projects = new LinkedHashMap<String, ProjectModel>();
+ for (RepositoryModel repository : repositoryModels) {
+ if (!includeUsers && repository.isPersonalRepository()) {
+ // exclude personal repositories
+ continue;
+ }
+ if (!projects.containsKey(repository.projectPath)) {
+ ProjectModel project = getProjectModel(repository.projectPath);
+ if (project == null) {
+ logger.warn(MessageFormat.format("excluding project \"{0}\" from project list because it is empty!",
+ repository.projectPath));
+ continue;
+ }
+ projects.put(repository.projectPath, project);
+ // clear the repo list in the project because that is the system
+ // list, not the user-accessible list and start building the
+ // user-accessible list
+ project.repositories.clear();
+ project.repositories.add(repository.name);
+ project.lastChange = repository.lastChange;
+ } else {
+ // update the user-accessible list
+ // this is used for repository count
+ ProjectModel project = projects.get(repository.projectPath);
+ project.repositories.add(repository.name);
+ if (project.lastChange.before(repository.lastChange)) {
+ project.lastChange = repository.lastChange;
+ }
+ }
+ }
+ return new ArrayList<ProjectModel>(projects.values());
+ }
+
+ /**
+ * 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;
+ }
+ RepositoryModel model = new RepositoryModel();
+ model.isBare = r.isBare();
+ File basePath = getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
+ if (model.isBare) {
+ model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory());
+ } else {
+ model.name = com.gitblit.utils.FileUtils.getRelativePath(basePath, r.getDirectory().getParentFile());
+ }
+ if (StringUtils.isEmpty(model.name)) {
+ // Repository is NOT located relative to the base folder because it
+ // is symlinked. Use the provided repository name.
+ model.name = repositoryName;
+ }
+ model.hasCommits = JGitUtils.hasCommits(r);
+ model.lastChange = JGitUtils.getLastChange(r);
+ model.projectPath = StringUtils.getFirstPathElement(repositoryName);
+
+ StoredConfig config = r.getConfig();
+ boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
+
+ if (config != null) {
+ model.description = getConfig(config, "description", "");
+ model.addOwners(ArrayUtils.fromString(getConfig(config, "owner", "")));
+ model.useTickets = getConfig(config, "useTickets", false);
+ model.useDocs = getConfig(config, "useDocs", false);
+ model.useIncrementalPushTags = getConfig(config, "useIncrementalPushTags", false);
+ model.incrementalPushTagPrefix = getConfig(config, "incrementalPushTagPrefix", null);
+ model.allowForks = getConfig(config, "allowForks", true);
+ model.accessRestriction = AccessRestrictionType.fromName(getConfig(config,
+ "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null)));
+ model.authorizationControl = AuthorizationControl.fromName(getConfig(config,
+ "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null)));
+ model.verifyCommitter = getConfig(config, "verifyCommitter", false);
+ model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin);
+ 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(
+ Constants.CONFIG_GITBLIT, null, "federationSets")));
+ model.isFederated = getConfig(config, "isFederated", false);
+ model.gcThreshold = getConfig(config, "gcThreshold", settings.getString(Keys.git.defaultGarbageCollectionThreshold, "500KB"));
+ model.gcPeriod = getConfig(config, "gcPeriod", settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7));
+ try {
+ model.lastGC = new SimpleDateFormat(Constants.ISO8601).parse(getConfig(config, "lastGC", "1970-01-01'T'00:00:00Z"));
+ } catch (Exception e) {
+ model.lastGC = new Date(0);
+ }
+ model.maxActivityCommits = getConfig(config, "maxActivityCommits", settings.getInteger(Keys.web.maxActivityCommits, 0));
+ model.origin = config.getString("remote", "origin", "url");
+ if (model.origin != null) {
+ model.origin = model.origin.replace('\\', '/');
+ }
+ model.preReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
+ Constants.CONFIG_GITBLIT, null, "preReceiveScript")));
+ model.postReceiveScripts = new ArrayList<String>(Arrays.asList(config.getStringList(
+ Constants.CONFIG_GITBLIT, null, "postReceiveScript")));
+ model.mailingLists = new ArrayList<String>(Arrays.asList(config.getStringList(
+ 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);
+ model.sparkleshareId = JGitUtils.getSparkleshareId(r);
+ r.close();
+
+ if (model.origin != null && model.origin.startsWith("file://")) {
+ // repository was cloned locally... perhaps as a fork
+ try {
+ File folder = new File(new URI(model.origin));
+ String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder);
+ if (!StringUtils.isEmpty(originRepo)) {
+ // ensure origin still exists
+ File repoFolder = new File(getRepositoriesFolder(), originRepo);
+ if (repoFolder.exists()) {
+ model.originRepository = originRepo.toLowerCase();
+ }
+ }
+ } catch (URISyntaxException e) {
+ logger.error("Failed to determine fork for " + model, e);
+ }
+ }
+ return model;
+ }
+
+ /**
+ * Determines if this server has the requested repository.
+ *
+ * @param n
+ * @return true if the repository exists
+ */
+ public boolean hasRepository(String repositoryName) {
+ return hasRepository(repositoryName, false);
+ }
+
+ /**
+ * Determines if this server has the requested repository.
+ *
+ * @param n
+ * @param caseInsensitive
+ * @return true if the repository exists
+ */
+ public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
+ 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());
+ }
+ Repository r = getRepository(repositoryName, false);
+ if (r == null) {
+ return false;
+ }
+ r.close();
+ return true;
+ }
+
+ /**
+ * Determines if the specified user has a fork of the specified origin
+ * repository.
+ *
+ * @param username
+ * @param origin
+ * @return true the if the user has a fork
+ */
+ public boolean hasFork(String username, String origin) {
+ return getFork(username, origin) != null;
+ }
+
+ /**
+ * Gets the name of a user's fork of the specified origin
+ * repository.
+ *
+ * @param username
+ * @param origin
+ * @return the name of the user's fork, null otherwise
+ */
+ public String getFork(String username, String origin) {
+ String userProject = "~" + username.toLowerCase();
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ String userPath = userProject + "/";
+
+ // collect all origin nodes in fork network
+ Set<String> roots = new HashSet<String>();
+ roots.add(origin);
+ RepositoryModel originModel = repositoryListCache.get(origin);
+ while (originModel != null) {
+ if (!ArrayUtils.isEmpty(originModel.forks)) {
+ for (String fork : originModel.forks) {
+ if (!fork.startsWith(userPath)) {
+ roots.add(fork);
+ }
+ }
+ }
+
+ if (originModel.originRepository != null) {
+ roots.add(originModel.originRepository);
+ originModel = repositoryListCache.get(originModel.originRepository);
+ } else {
+ // break
+ originModel = null;
+ }
+ }
+
+ for (String repository : repositoryListCache.keySet()) {
+ if (repository.startsWith(userPath)) {
+ RepositoryModel model = repositoryListCache.get(repository);
+ if (!StringUtils.isEmpty(model.originRepository)) {
+ if (roots.contains(model.originRepository)) {
+ // user has a fork in this graph
+ return model.name;
+ }
+ }
+ }
+ }
+ } else {
+ // not caching
+ ProjectModel project = getProjectModel(userProject);
+ if (project == null) {
+ return null;
+ }
+ for (String repository : project.repositories) {
+ if (repository.startsWith(userProject)) {
+ RepositoryModel model = getRepositoryModel(repository);
+ if (model.originRepository.equalsIgnoreCase(origin)) {
+ // user has a fork
+ return model.name;
+ }
+ }
+ }
+ }
+ // user does not have a fork
+ return null;
+ }
+
+ /**
+ * Returns the fork network for a repository by traversing up the fork graph
+ * to discover the root and then down through all children of the root node.
+ *
+ * @param repository
+ * @return a ForkModel
+ */
+ public ForkModel getForkNetwork(String repository) {
+ if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+ // find the root, cached
+ RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
+ while (model.originRepository != null) {
+ model = repositoryListCache.get(model.originRepository);
+ }
+ ForkModel root = getForkModelFromCache(model.name);
+ return root;
+ } else {
+ // find the root, non-cached
+ RepositoryModel model = getRepositoryModel(repository.toLowerCase());
+ while (model.originRepository != null) {
+ model = getRepositoryModel(model.originRepository);
+ }
+ ForkModel root = getForkModel(model.name);
+ return root;
+ }
+ }
+
+ private ForkModel getForkModelFromCache(String repository) {
+ RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
+ if (model == null) {
+ return null;
+ }
+ ForkModel fork = new ForkModel(model);
+ if (!ArrayUtils.isEmpty(model.forks)) {
+ for (String aFork : model.forks) {
+ ForkModel fm = getForkModelFromCache(aFork);
+ if (fm != null) {
+ fork.forks.add(fm);
+ }
+ }
+ }
+ return fork;
+ }
+
+ private ForkModel getForkModel(String repository) {
+ RepositoryModel model = getRepositoryModel(repository.toLowerCase());
+ if (model == null) {
+ return null;
+ }
+ ForkModel fork = new ForkModel(model);
+ if (!ArrayUtils.isEmpty(model.forks)) {
+ for (String aFork : model.forks) {
+ ForkModel fm = getForkModel(aFork);
+ if (fm != null) {
+ fork.forks.add(fm);
+ }
+ }
+ }
+ return fork;
+ }
+
+ /**
+ * 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);
+ long size = com.gitblit.utils.FileUtils.folderSize(gitDir);
+ repositorySizeCache.updateObject(model.name, model.lastChange, size);
+ return size;
+ }
+
+ /**
+ * Ensure that a cached repository is completely closed and its resources
+ * are properly released.
+ *
+ * @param repositoryName
+ */
+ private void closeRepository(String repositoryName) {
+ Repository repository = getRepository(repositoryName);
+ if (repository == null) {
+ return;
+ }
+ RepositoryCache.close(repository);
+
+ // assume 2 uses in case reflection fails
+ int uses = 2;
+ try {
+ // The FileResolver caches repositories which is very useful
+ // for performance until you want to delete a repository.
+ // I have to use reflection to call close() the correct
+ // number of times to ensure that the object and ref databases
+ // are properly closed before I can delete the repository from
+ // the filesystem.
+ Field useCnt = Repository.class.getDeclaredField("useCnt");
+ useCnt.setAccessible(true);
+ uses = ((AtomicInteger) useCnt.get(repository)).get();
+ } catch (Exception e) {
+ logger.warn(MessageFormat
+ .format("Failed to reflectively determine use count for repository {0}",
+ repositoryName), e);
+ }
+ if (uses > 0) {
+ logger.info(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++) {
+ repository.close();
+ }
+ }
+
+ // close any open index writer/searcher in the Lucene executor
+ luceneExecutor.close(repositoryName);
+ }
+
+ /**
+ * 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, getTimezone());
+ 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
+ * @param field
+ * @param defaultValue
+ * @return field value or defaultValue
+ */
+ private String getConfig(StoredConfig config, String field, String defaultValue) {
+ String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
+ if (StringUtils.isEmpty(value)) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ /**
+ * Returns the gitblit boolean value for the specified key. If key is not
+ * set, returns defaultValue.
+ *
+ * @param config
+ * @param field
+ * @param defaultValue
+ * @return field value or defaultValue
+ */
+ private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
+ return config.getBoolean(Constants.CONFIG_GITBLIT, field, defaultValue);
+ }
+
+ /**
+ * Returns the gitblit string value for the specified key. If key is not
+ * set, returns defaultValue.
+ *
+ * @param config
+ * @param field
+ * @param defaultValue
+ * @return field value or defaultValue
+ */
+ private int getConfig(StoredConfig config, String field, int defaultValue) {
+ String value = config.getString(Constants.CONFIG_GITBLIT, null, field);
+ if (StringUtils.isEmpty(value)) {
+ return defaultValue;
+ }
+ try {
+ return Integer.parseInt(value);
+ } catch (Exception e) {
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Creates/updates the repository model keyed by reopsitoryName. Saves all
+ * repository settings in .git/config. This method allows for renaming
+ * repositories and will update user access permissions accordingly.
+ *
+ * All repositories created by this method are bare and automatically have
+ * .git appended to their names, which is the standard convention for bare
+ * repositories.
+ *
+ * @param repositoryName
+ * @param repository
+ * @param isCreate
+ * @throws GitBlitException
+ */
+ public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
+ boolean isCreate) throws GitBlitException {
+ if (gcExecutor.isCollectingGarbage(repositoryName)) {
+ throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}",
+ repositoryName));
+ }
+ Repository r = null;
+ String projectPath = StringUtils.getFirstPathElement(repository.name);
+ if (!StringUtils.isEmpty(projectPath)) {
+ if (projectPath.equalsIgnoreCase(getString(Keys.web.repositoryRootGroupName, "main"))) {
+ // strip leading group name
+ repository.name = repository.name.substring(projectPath.length() + 1);
+ }
+ }
+ if (isCreate) {
+ // ensure created repository name ends with .git
+ if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
+ repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
+ }
+ if (hasRepository(repository.name)) {
+ throw new GitBlitException(MessageFormat.format(
+ "Can not create repository ''{0}'' because it already exists.",
+ repository.name));
+ }
+ // create repository
+ logger.info("create repository " + repository.name);
+ r = JGitUtils.createRepository(repositoriesFolder, repository.name);
+ } 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);
+ if (destFolder.exists()) {
+ throw new GitBlitException(
+ MessageFormat
+ .format("Can not rename repository ''{0}'' to ''{1}'' because ''{1}'' already exists.",
+ repositoryName, repository.name));
+ }
+ File parentFile = destFolder.getParentFile();
+ if (!parentFile.exists() && !parentFile.mkdirs()) {
+ throw new GitBlitException(MessageFormat.format(
+ "Failed to create folder ''{0}''", parentFile.getAbsolutePath()));
+ }
+ if (!folder.renameTo(destFolder)) {
+ throw new GitBlitException(MessageFormat.format(
+ "Failed to rename repository ''{0}'' to ''{1}''.", repositoryName,
+ repository.name));
+ }
+ // rename the roles
+ if (!userService.renameRepositoryRole(repositoryName, repository.name)) {
+ throw new GitBlitException(MessageFormat.format(
+ "Failed to rename repository permissions ''{0}'' to ''{1}''.",
+ repositoryName, repository.name));
+ }
+
+ // rename fork origins in their configs
+ if (!ArrayUtils.isEmpty(repository.forks)) {
+ for (String fork : repository.forks) {
+ Repository rf = getRepository(fork);
+ try {
+ StoredConfig config = rf.getConfig();
+ String origin = config.getString("remote", "origin", "url");
+ origin = origin.replace(repositoryName, repository.name);
+ config.setString("remote", "origin", "url", origin);
+ config.save();
+ } catch (Exception e) {
+ logger.error("Failed to update repository fork config for " + fork, e);
+ }
+ rf.close();
+ }
+ }
+
+ // remove this repository from any origin model's fork list
+ if (!StringUtils.isEmpty(repository.originRepository)) {
+ RepositoryModel origin = repositoryListCache.get(repository.originRepository);
+ if (origin != null && !ArrayUtils.isEmpty(origin.forks)) {
+ origin.forks.remove(repositoryName);
+ }
+ }
+
+ // clear the cache
+ clearRepositoryMetadataCache(repositoryName);
+ repository.resetDisplayName();
+ }
+
+ // load repository
+ logger.info("edit repository " + repository.name);
+ r = getRepository(repository.name);
+ }
+
+ // update settings
+ if (r != null) {
+ updateConfiguration(r, repository);
+ // only update symbolic head if it changes
+ String currentRef = JGitUtils.getHEADRef(r);
+ if (!StringUtils.isEmpty(repository.HEAD) && !repository.HEAD.equals(currentRef)) {
+ logger.info(MessageFormat.format("Relinking {0} HEAD from {1} to {2}",
+ repository.name, currentRef, repository.HEAD));
+ if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
+ // clear the cache
+ 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);
+ }
+
+ /**
+ * Updates the Gitblit configuration for the specified repository.
+ *
+ * @param r
+ * the Git repository
+ * @param repository
+ * the Gitblit repository model
+ */
+ public void updateConfiguration(Repository r, RepositoryModel repository) {
+ StoredConfig config = r.getConfig();
+ config.setString(Constants.CONFIG_GITBLIT, null, "description", repository.description);
+ config.setString(Constants.CONFIG_GITBLIT, null, "owner", ArrayUtils.toString(repository.owners));
+ config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets);
+ config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs);
+ config.setBoolean(Constants.CONFIG_GITBLIT, null, "useIncrementalPushTags", repository.useIncrementalPushTags);
+ if (StringUtils.isEmpty(repository.incrementalPushTagPrefix) ||
+ repository.incrementalPushTagPrefix.equals(settings.getString(Keys.git.defaultIncrementalPushTagPrefix, "r"))) {
+ config.unset(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix");
+ } else {
+ config.setString(Constants.CONFIG_GITBLIT, null, "incrementalPushTagPrefix", repository.incrementalPushTagPrefix);
+ }
+ config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks);
+ 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, "verifyCommitter", repository.verifyCommitter);
+ 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(Constants.CONFIG_GITBLIT, null, "isFederated", repository.isFederated);
+ config.setString(Constants.CONFIG_GITBLIT, null, "gcThreshold", repository.gcThreshold);
+ if (repository.gcPeriod == settings.getInteger(Keys.git.defaultGarbageCollectionPeriod, 7)) {
+ // use default from config
+ config.unset(Constants.CONFIG_GITBLIT, null, "gcPeriod");
+ } else {
+ config.setInt(Constants.CONFIG_GITBLIT, null, "gcPeriod", repository.gcPeriod);
+ }
+ if (repository.lastGC != null) {
+ config.setString(Constants.CONFIG_GITBLIT, null, "lastGC", new SimpleDateFormat(Constants.ISO8601).format(repository.lastGC));
+ }
+ if (repository.maxActivityCommits == settings.getInteger(Keys.web.maxActivityCommits, 0)) {
+ // use default from config
+ config.unset(Constants.CONFIG_GITBLIT, null, "maxActivityCommits");
+ } else {
+ config.setInt(Constants.CONFIG_GITBLIT, null, "maxActivityCommits", repository.maxActivityCommits);
+ }
+
+ 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);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+
+ /**
+ * Deletes the repository from the file system and removes the repository
+ * permission from all repository users.
+ *
+ * @param model
+ * @return true if successful
+ */
+ public boolean deleteRepositoryModel(RepositoryModel model) {
+ return deleteRepository(model.name);
+ }
+
+ /**
+ * Deletes the repository from the file system and removes the repository
+ * permission from all repository users.
+ *
+ * @param repositoryName
+ * @return true if successful
+ */
+ public boolean deleteRepository(String repositoryName) {
+ try {
+ closeRepository(repositoryName);
+ // clear the repository cache
+ clearRepositoryMetadataCache(repositoryName);
+
+ RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
+ if (model != null && !ArrayUtils.isEmpty(model.forks)) {
+ resetRepositoryListCache();
+ }
+
+ 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;
+ }
+ }
+ } catch (Throwable t) {
+ logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
+ }
+ return false;
+ }
+
+ /**
+ * Returns an html version of the commit message with any global or
+ * repository-specific regular expression substitution applied.
+ *
+ * @param repositoryName
+ * @param text
+ * @return html version of the commit message
+ */
+ public String processCommitMessage(String repositoryName, String text) {
+ String html = StringUtils.breakLinesForHtml(text);
+ Map<String, String> map = new HashMap<String, String>();
+ // global regex keys
+ if (settings.getBoolean(Keys.regex.global, false)) {
+ for (String key : settings.getAllKeys(Keys.regex.global)) {
+ if (!key.equals(Keys.regex.global)) {
+ String subKey = key.substring(key.lastIndexOf('.') + 1);
+ map.put(subKey, settings.getString(key, ""));
+ }
+ }
+ }
+
+ // repository-specific regex keys
+ List<String> keys = settings.getAllKeys(Keys.regex._ROOT + "."
+ + repositoryName.toLowerCase());
+ for (String key : keys) {
+ String subKey = key.substring(key.lastIndexOf('.') + 1);
+ map.put(subKey, settings.getString(key, ""));
+ }
+
+ for (Entry<String, String> entry : map.entrySet()) {
+ String definition = entry.getValue().trim();
+ String[] chunks = definition.split("!!!");
+ if (chunks.length == 2) {
+ html = html.replaceAll(chunks[0], chunks[1]);
+ } else {
+ logger.warn(entry.getKey()
+ + " improperly formatted. Use !!! to separate match from replacement: "
+ + definition);
+ }
+ }
+ return html;
+ }
+
+ /**
+ * Returns Gitblit's scheduled executor service for scheduling tasks.
+ *
+ * @return scheduledExecutor
+ */
+ public ScheduledExecutorService executor() {
+ return scheduledExecutor;
+ }
+
+ public static boolean canFederate() {
+ String passphrase = getString(Keys.federation.passphrase, "");
+ return !StringUtils.isEmpty(passphrase);
+ }
+
+ /**
+ * Configures this Gitblit instance to pull any registered federated gitblit
+ * instances.
+ */
+ private void configureFederation() {
+ 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 (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) {
+ FederationPullExecutor executor = new FederationPullExecutor(registrations, true);
+ scheduledExecutor.schedule(executor, 1, TimeUnit.MINUTES);
+ }
+ }
+
+ /**
+ * Returns the list of federated gitblit instances that this instance will
+ * try to pull.
+ *
+ * @return list of registered gitblit instances
+ */
+ public List<FederationModel> getFederationRegistrations() {
+ if (federationRegistrations.isEmpty()) {
+ federationRegistrations.addAll(FederationUtils.getFederationRegistrations(settings));
+ }
+ return federationRegistrations;
+ }
+
+ /**
+ * Retrieve the specified federation registration.
+ *
+ * @param name
+ * the name of the registration
+ * @return a federation registration
+ */
+ public FederationModel getFederationRegistration(String url, String name) {
+ // check registrations
+ for (FederationModel r : getFederationRegistrations()) {
+ if (r.name.equals(name) && r.url.equals(url)) {
+ return r;
+ }
+ }
+
+ // check the results
+ for (FederationModel r : getFederationResultRegistrations()) {
+ if (r.name.equals(name) && r.url.equals(url)) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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;
+ }
+
+ /**
+ * Returns the specified federation token for this Gitblit instance.
+ *
+ * @param type
+ * @return a federation token
+ */
+ public String getFederationToken(FederationToken type) {
+ 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);
+ }
+
+ /**
+ * Compares the provided token with this Gitblit instance's tokens and
+ * determines if the requested permission may be granted to the token.
+ *
+ * @param req
+ * @param token
+ * @return true if the request can be executed
+ */
+ public boolean validateFederationRequest(FederationRequest req, String token) {
+ String all = getFederationToken(FederationToken.ALL);
+ String unr = getFederationToken(FederationToken.USERS_AND_REPOSITORIES);
+ String jur = getFederationToken(FederationToken.REPOSITORIES);
+ switch (req) {
+ case PULL_REPOSITORIES:
+ return token.equals(all) || token.equals(unr) || token.equals(jur);
+ case PULL_USERS:
+ case PULL_TEAMS:
+ return token.equals(all) || token.equals(unr);
+ case PULL_SETTINGS:
+ case PULL_SCRIPTS:
+ return token.equals(all);
+ default:
+ break;
+ }
+ return false;
+ }
+
+ /**
+ * Acknowledge and cache the status of a remote Gitblit instance.
+ *
+ * @param identification
+ * the identification of the pulling Gitblit instance
+ * @param registration
+ * the registration from the pulling Gitblit instance
+ * @return true if acknowledged
+ */
+ public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
+ // reset the url to the identification of the pulling Gitblit instance
+ registration.url = identification;
+ String id = identification;
+ if (!StringUtils.isEmpty(registration.folder)) {
+ id += "-" + registration.folder;
+ }
+ federationPullResults.put(id, registration);
+ return true;
+ }
+
+ /**
+ * Returns the list of registration results.
+ *
+ * @return the list of registration results
+ */
+ public List<FederationModel> getFederationResultRegistrations() {
+ return new ArrayList<FederationModel>(federationPullResults.values());
+ }
+
+ /**
+ * Submit a federation proposal. The proposal is cached locally and the
+ * Gitblit administrator(s) are notified via email.
+ *
+ * @param proposal
+ * the proposal
+ * @param gitblitUrl
+ * 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
+ String json = JsonUtils.toJsonString(proposal);
+
+ try {
+ // make the proposals folder
+ File proposalsFolder = getProposalsFolder();
+ proposalsFolder.mkdirs();
+
+ // cache json to a file
+ File file = new File(proposalsFolder, proposal.token + Constants.PROPOSAL_EXT);
+ com.gitblit.utils.FileUtils.writeContent(file, json);
+ } catch (Exception e) {
+ logger.error(MessageFormat.format("Failed to cache proposal from {0}", proposal.url), e);
+ }
+
+ // send an email, if possible
+ sendMailToAdministrators("Federation proposal from " + proposal.url,
+ "Please review the proposal @ " + gitblitUrl + "/proposal/" + proposal.token);
+ return true;
+ }
+
+ /**
+ * Returns the list of pending federation proposals
+ *
+ * @return list of federation proposals
+ */
+ public List<FederationProposal> getPendingFederationProposals() {
+ List<FederationProposal> list = new ArrayList<FederationProposal>();
+ File folder = getProposalsFolder();
+ if (folder.exists()) {
+ File[] files = folder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isFile()
+ && file.getName().toLowerCase().endsWith(Constants.PROPOSAL_EXT);
+ }
+ });
+ for (File file : files) {
+ String json = com.gitblit.utils.FileUtils.readContent(file, null);
+ 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;
+ default:
+ 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;
+ }
+
+ /**
+ * Returns the proposal identified by the supplied token.
+ *
+ * @param token
+ * @return the specified proposal or null
+ */
+ public FederationProposal getPendingFederationProposal(String token) {
+ List<FederationProposal> list = getPendingFederationProposals();
+ for (FederationProposal proposal : list) {
+ if (proposal.token.equals(token)) {
+ return proposal;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Deletes a pending federation proposal.
+ *
+ * @param a
+ * proposal
+ * @return true if the proposal was deleted
+ */
+ public boolean deletePendingFederationProposal(FederationProposal proposal) {
+ File folder = getProposalsFolder();
+ File file = new File(folder, proposal.token + Constants.PROPOSAL_EXT);
+ return file.delete();
+ }
+
+ /**
+ * Returns the list of all Groovy push hook scripts. Script files must have
+ * .groovy extension
+ *
+ * @return list of available hook scripts
+ */
+ public List<String> getAllScripts() {
+ File groovyFolder = getGroovyScriptsFolder();
+ File[] files = groovyFolder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File pathname) {
+ return pathname.isFile() && pathname.getName().endsWith(".groovy");
+ }
+ });
+ List<String> scripts = new ArrayList<String>();
+ if (files != null) {
+ for (File file : files) {
+ String script = file.getName().substring(0, file.getName().lastIndexOf('.'));
+ scripts.add(script);
+ }
+ }
+ return scripts;
+ }
+
+ /**
+ * Returns the list of pre-receive scripts the repository inherited from the
+ * global settings and team affiliations.
+ *
+ * @param repository
+ * if null only the globally specified scripts are returned
+ * @return a list of scripts
+ */
+ public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
+ Set<String> scripts = new LinkedHashSet<String>();
+ // Globals
+ for (String script : getStrings(Keys.groovy.preReceiveScripts)) {
+ if (script.endsWith(".groovy")) {
+ scripts.add(script.substring(0, script.lastIndexOf('.')));
+ } else {
+ scripts.add(script);
+ }
+ }
+
+ // Team Scripts
+ if (repository != null) {
+ for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
+ TeamModel team = userService.getTeamModel(teamname);
+ scripts.addAll(team.preReceiveScripts);
+ }
+ }
+ return new ArrayList<String>(scripts);
+ }
+
+ /**
+ * Returns the list of all available Groovy pre-receive push hook scripts
+ * that are not already inherited by the repository. Script files must have
+ * .groovy extension
+ *
+ * @param repository
+ * optional parameter
+ * @return list of available hook scripts
+ */
+ public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
+ Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository));
+
+ // create list of available scripts by excluding inherited scripts
+ List<String> scripts = new ArrayList<String>();
+ for (String script : getAllScripts()) {
+ if (!inherited.contains(script)) {
+ scripts.add(script);
+ }
+ }
+ return scripts;
+ }
+
+ /**
+ * Returns the list of post-receive scripts the repository inherited from
+ * the global settings and team affiliations.
+ *
+ * @param repository
+ * if null only the globally specified scripts are returned
+ * @return a list of scripts
+ */
+ public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
+ Set<String> scripts = new LinkedHashSet<String>();
+ // Global Scripts
+ for (String script : getStrings(Keys.groovy.postReceiveScripts)) {
+ if (script.endsWith(".groovy")) {
+ scripts.add(script.substring(0, script.lastIndexOf('.')));
+ } else {
+ scripts.add(script);
+ }
+ }
+ // Team Scripts
+ if (repository != null) {
+ for (String teamname : userService.getTeamnamesForRepositoryRole(repository.name)) {
+ TeamModel team = userService.getTeamModel(teamname);
+ scripts.addAll(team.postReceiveScripts);
+ }
+ }
+ return new ArrayList<String>(scripts);
+ }
+
+ /**
+ * Returns the list of unused Groovy post-receive push hook scripts that are
+ * not already inherited by the repository. Script files must have .groovy
+ * extension
+ *
+ * @param repository
+ * optional parameter
+ * @return list of available hook scripts
+ */
+ public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
+ Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository));
+
+ // create list of available scripts by excluding inherited scripts
+ List<String> scripts = new ArrayList<String>();
+ for (String script : getAllScripts()) {
+ if (!inherited.contains(script)) {
+ scripts.add(script);
+ }
+ }
+ 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.
+ *
+ * @param subject
+ * @param message
+ */
+ public void sendMailToAdministrators(String subject, String message) {
+ List<String> toAddresses = settings.getStrings(Keys.mail.adminAddresses);
+ sendMail(subject, message, toAddresses);
+ }
+
+ /**
+ * Notify users by email of something.
+ *
+ * @param subject
+ * @param message
+ * @param toAddresses
+ */
+ public void sendMail(String subject, String message, Collection<String> toAddresses) {
+ this.sendMail(subject, message, toAddresses.toArray(new String[0]));
+ }
+
+ /**
+ * Notify users by email of something.
+ *
+ * @param subject
+ * @param message
+ * @param toAddresses
+ */
+ public void sendMail(String subject, String message, String... toAddresses) {
+ if (toAddresses == null || toAddresses.length == 0) {
+ logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject));
+ return;
+ }
+ try {
+ Message mail = mailExecutor.createMessage(toAddresses);
+ if (mail != null) {
+ mail.setSubject(subject);
+
+ MimeBodyPart messagePart = new MimeBodyPart();
+ messagePart.setText(message, "utf-8");
+ messagePart.setHeader("Content-Type", "text/plain; charset=\"utf-8\"");
+ messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable");
+
+ MimeMultipart multiPart = new MimeMultipart();
+ multiPart.addBodyPart(messagePart);
+ mail.setContent(multiPart);
+
+ mailExecutor.queue(mail);
+ }
+ } catch (MessagingException e) {
+ logger.error("Messaging error", e);
+ }
+ }
+
+ /**
+ * Notify users by email of something.
+ *
+ * @param subject
+ * @param message
+ * @param toAddresses
+ */
+ public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
+ this.sendHtmlMail(subject, message, toAddresses.toArray(new String[0]));
+ }
+
+ /**
+ * Notify users by email of something.
+ *
+ * @param subject
+ * @param message
+ * @param toAddresses
+ */
+ public void sendHtmlMail(String subject, String message, String... toAddresses) {
+ if (toAddresses == null || toAddresses.length == 0) {
+ logger.debug(MessageFormat.format("Dropping message {0} because there are no recipients", subject));
+ return;
+ }
+ try {
+ Message mail = mailExecutor.createMessage(toAddresses);
+ if (mail != null) {
+ mail.setSubject(subject);
+
+ MimeBodyPart messagePart = new MimeBodyPart();
+ messagePart.setText(message, "utf-8");
+ messagePart.setHeader("Content-Type", "text/html; charset=\"utf-8\"");
+ messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable");
+
+ MimeMultipart multiPart = new MimeMultipart();
+ multiPart.addBodyPart(messagePart);
+ mail.setContent(multiPart);
+
+ mailExecutor.queue(mail);
+ }
+ } catch (MessagingException e) {
+ logger.error("Messaging error", e);
+ }
+ }
+
+ /**
+ * 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
+ for (String key : settings.getAllKeys(null)) {
+ SettingModel setting = settingsModel.get(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;
+ }
+
+ /**
+ * 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();
+ 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
+ // models map.
+ InputStream is = getClass().getResourceAsStream("/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, File folder, boolean startFederation) {
+ this.settings = settings;
+ this.baseFolder = folder;
+
+ repositoriesFolder = getRepositoriesFolder();
+
+ logger.info("Gitblit base folder = " + folder.getAbsolutePath());
+ logger.info("Git repositories folder = " + repositoriesFolder.getAbsolutePath());
+ logger.info("Gitblit settings = " + settings.toString());
+
+ // prepare service executors
+ mailExecutor = new MailExecutor(settings);
+ luceneExecutor = new LuceneExecutor(settings, repositoriesFolder);
+ gcExecutor = new GCExecutor(settings);
+
+ // 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());
+
+ serverStatus = new ServerStatus(isGO());
+
+ if (this.userService == null) {
+ String realm = settings.getString(Keys.realm.userService, "${baseFolder}/users.properties");
+ IUserService loginService = null;
+ try {
+ // check to see if this "file" is a login service class
+ Class<?> realmClass = Class.forName(realm);
+ loginService = (IUserService) realmClass.newInstance();
+ } catch (Throwable t) {
+ loginService = new GitblitUserService();
+ }
+ setUserService(loginService);
+ }
+
+ // load and cache the project metadata
+ projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
+ getProjectConfigs();
+
+ configureMailExecutor();
+ configureLuceneIndexing();
+ configureGarbageCollector();
+ if (startFederation) {
+ configureFederation();
+ }
+ configureJGit();
+ configureFanout();
+ configureGitDaemon();
+
+ ContainerUtils.CVE_2007_0450.test();
+ }
+
+ protected void configureMailExecutor() {
+ 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.");
+ }
+ }
+
+ protected void configureLuceneIndexing() {
+ scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2, TimeUnit.MINUTES);
+ logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
+ }
+
+ protected void configureGarbageCollector() {
+ // schedule gc engine
+ if (gcExecutor.isReady()) {
+ logger.info("GC executor is scheduled to 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);
+ c.set(Calendar.SECOND, 0);
+ c.set(Calendar.MILLISECOND, 0);
+ Date cd = c.getTime();
+ Date now = new Date();
+ int delay = 0;
+ if (cd.before(now)) {
+ c.add(Calendar.DATE, 1);
+ cd = c.getTime();
+ }
+ delay = (int) ((cd.getTime() - now.getTime())/TimeUtils.MIN);
+ String when = delay + " mins";
+ if (delay > 60) {
+ when = MessageFormat.format("{0,number,0.0} hours", ((float)delay)/60f);
+ }
+ logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when));
+ scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60*24, TimeUnit.MINUTES);
+ }
+ }
+
+ protected void configureJGit() {
+ // 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 {
+ cfg.install();
+ 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);
+ }
+ }
+
+ protected void configureFanout() {
+ // startup Fanout PubSub service
+ if (settings.getInteger(Keys.fanout.port, 0) > 0) {
+ String bindInterface = settings.getString(Keys.fanout.bindInterface, null);
+ int port = settings.getInteger(Keys.fanout.port, FanoutService.DEFAULT_PORT);
+ boolean useNio = settings.getBoolean(Keys.fanout.useNio, true);
+ int limit = settings.getInteger(Keys.fanout.connectionLimit, 0);
+
+ if (useNio) {
+ if (StringUtils.isEmpty(bindInterface)) {
+ fanoutService = new FanoutNioService(port);
+ } else {
+ fanoutService = new FanoutNioService(bindInterface, port);
+ }
+ } else {
+ if (StringUtils.isEmpty(bindInterface)) {
+ fanoutService = new FanoutSocketService(port);
+ } else {
+ fanoutService = new FanoutSocketService(bindInterface, port);
+ }
+ }
+
+ fanoutService.setConcurrentConnectionLimit(limit);
+ fanoutService.setAllowAllChannelAnnouncements(false);
+ fanoutService.start();
+ }
+ }
+
+ protected void configureGitDaemon() {
+ int port = settings.getInteger(Keys.git.daemonPort, 0);
+ String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
+ if (port > 0) {
+ try {
+ gitDaemon = new GitDaemon(bindInterface, port, getRepositoriesFolder());
+ gitDaemon.start();
+ } catch (IOException e) {
+ gitDaemon = null;
+ logger.error(MessageFormat.format("Failed to start Git daemon on {0}:{1,number,0}", bindInterface, port), e);
+ }
+ }
+ }
+
+ protected final Logger getLogger() {
+ return logger;
+ }
+
+ protected final ScheduledExecutorService getScheduledExecutor() {
+ return scheduledExecutor;
+ }
+
+ protected final LuceneExecutor getLuceneExecutor() {
+ return luceneExecutor;
+ }
+
+ private void logTimezone(String type, TimeZone zone) {
+ SimpleDateFormat df = new SimpleDateFormat("z Z");
+ df.setTimeZone(zone);
+ String offset = df.format(new Date());
+ logger.info(type + " timezone is " + zone.getID() + " (" + offset + ")");
+ }
+
+ /**
+ * Configure Gitblit from the web.xml, if no configuration has already been
+ * specified.
+ *
+ * @see ServletContextListener.contextInitialize(ServletContextEvent)
+ */
+ @Override
+ public void contextInitialized(ServletContextEvent contextEvent) {
+ servletContext = contextEvent.getServletContext();
+ if (settings == null) {
+ // Gitblit is running in a servlet container
+ ServletContext context = contextEvent.getServletContext();
+ WebXmlSettings webxmlSettings = new WebXmlSettings(context);
+ String contextRealPath = context.getRealPath("/");
+ File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
+ String openShift = System.getenv("OPENSHIFT_DATA_DIR");
+
+ if (!StringUtils.isEmpty(openShift)) {
+ // Gitblit is running in OpenShift/JBoss
+ File base = new File(openShift);
+ logger.info("EXPRESS contextFolder is " + contextFolder.getAbsolutePath());
+
+ // gitblit.properties setting overrides
+ File overrideFile = new File(base, "gitblit.properties");
+ webxmlSettings.applyOverrides(overrideFile);
+
+ // Copy the included scripts to the configured groovy folder
+ String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
+ File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
+ if (!localScripts.exists()) {
+ File warScripts = new File(contextFolder, "/WEB-INF/data/groovy");
+ if (!warScripts.equals(localScripts)) {
+ try {
+ com.gitblit.utils.FileUtils.copy(localScripts, warScripts.listFiles());
+ } catch (IOException e) {
+ logger.error(MessageFormat.format(
+ "Failed to copy included Groovy scripts from {0} to {1}",
+ warScripts, localScripts));
+ }
+ }
+ }
+
+ // configure context using the web.xml
+ configureContext(webxmlSettings, base, true);
+ } else {
+ // Gitblit is running in a standard servlet container
+ logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
+
+ String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
+
+ if (path.contains(Constants.contextFolder$) && contextFolder == null) {
+ // warn about null contextFolder (issue-199)
+ logger.error("");
+ logger.error(MessageFormat.format("\"{0}\" depends on \"{1}\" but \"{2}\" is returning NULL for \"{1}\"!",
+ Constants.baseFolder, Constants.contextFolder$, context.getServerInfo()));
+ logger.error(MessageFormat.format("Please specify a non-parameterized path for <context-param> {0} in web.xml!!", Constants.baseFolder));
+ logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
+ logger.error("");
+ }
+
+ File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
+ base.mkdirs();
+
+ // try to extract the data folder resource to the baseFolder
+ File localSettings = new File(base, "gitblit.properties");
+ if (!localSettings.exists()) {
+ extractResources(context, "/WEB-INF/data/", base);
+ }
+
+ // delegate all config to baseFolder/gitblit.properties file
+ FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
+ configureContext(settings, base, true);
+ }
+ }
+
+ settingsModel = loadSettingModels();
+ serverStatus.servletContainer = servletContext.getServerInfo();
+ }
+
+ protected void extractResources(ServletContext context, String path, File toDir) {
+ for (String resource : context.getResourcePaths(path)) {
+ // extract the resource to the directory if it does not exist
+ File f = new File(toDir, resource.substring(path.length()));
+ if (!f.exists()) {
InputStream is = null;
OutputStream os = null;
- try {
- if (resource.charAt(resource.length() - 1) == '/') {
- // directory
- f.mkdirs();
- extractResources(context, resource, f);
- } else {
- // file
- f.getParentFile().mkdirs();
+ try {
+ if (resource.charAt(resource.length() - 1) == '/') {
+ // directory
+ f.mkdirs();
+ extractResources(context, resource, f);
+ } else {
+ // file
+ f.getParentFile().mkdirs();
is = context.getResourceAsStream(resource);
os = new FileOutputStream(f);
- byte [] buffer = new byte[4096];
- int len = 0;
- while ((len = is.read(buffer)) > -1) {
- os.write(buffer, 0, len);
- }
- }
- } catch (FileNotFoundException e) {
- logger.error("Failed to find resource \"" + resource + "\"", e);
- } catch (IOException e) {
- logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
+ byte [] buffer = new byte[4096];
+ int len = 0;
+ while ((len = is.read(buffer)) > -1) {
+ os.write(buffer, 0, len);
+ }
+ }
+ } catch (FileNotFoundException e) {
+ logger.error("Failed to find resource \"" + resource + "\"", e);
+ } catch (IOException e) {
+ logger.error("Failed to copy resource \"" + resource + "\" to " + f, e);
} finally {
if (is != null) {
try {
@@ -3550,125 +3566,125 @@ public class GitBlit implements ServletContextListener {
// ignore
}
}
- }
- }
- }
- }
-
- /**
- * Gitblit is being shutdown either because the servlet container is
- * shutting down or because the servlet container is re-deploying Gitblit.
- */
- @Override
- public void contextDestroyed(ServletContextEvent contextEvent) {
- logger.info("Gitblit context destroyed by servlet container.");
- scheduledExecutor.shutdownNow();
- luceneExecutor.close();
- gcExecutor.close();
- if (fanoutService != null) {
- fanoutService.stop();
- }
- if (gitDaemon != null) {
- gitDaemon.stop();
- }
- }
-
- /**
- *
- * @return true if we are running the gc executor
- */
- public boolean isCollectingGarbage() {
- return gcExecutor.isRunning();
- }
-
- /**
- * Returns true if Gitblit is actively collecting garbage in this repository.
- *
- * @param repositoryName
- * @return true if actively collecting garbage
- */
- public boolean isCollectingGarbage(String repositoryName) {
- return gcExecutor.isCollectingGarbage(repositoryName);
- }
-
- /**
- * Creates a personal fork of the specified repository. The clone is view
- * restricted by default and the owner of the source repository is given
- * access to the clone.
- *
- * @param repository
- * @param user
- * @return the repository model of the fork, if successful
- * @throws GitBlitException
- */
- public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
- String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
- String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name);
-
- // clone the repository
- try {
- JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null);
- } catch (Exception e) {
- throw new GitBlitException(e);
- }
-
- // create a Gitblit repository model for the clone
- RepositoryModel cloneModel = repository.cloneAs(cloneName);
- // owner has REWIND/RW+ permissions
- cloneModel.addOwner(user.username);
- updateRepositoryModel(cloneName, cloneModel, false);
-
- // add the owner of the source repository to the clone's access list
- if (!ArrayUtils.isEmpty(repository.owners)) {
- for (String owner : repository.owners) {
- UserModel originOwner = getUserModel(owner);
- if (originOwner != null) {
- originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
- updateUserModel(originOwner.username, originOwner, false);
- }
- }
- }
-
- // grant origin's user list clone permission to fork
- List<String> users = getRepositoryUsers(repository);
- List<UserModel> cloneUsers = new ArrayList<UserModel>();
- for (String name : users) {
- if (!name.equalsIgnoreCase(user.username)) {
- UserModel cloneUser = getUserModel(name);
- if (cloneUser.canClone(repository)) {
- // origin user can clone origin, grant clone access to fork
- cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
- }
- cloneUsers.add(cloneUser);
- }
- }
- userService.updateUserModels(cloneUsers);
-
- // grant origin's team list clone permission to fork
- List<String> teams = getRepositoryTeams(repository);
- List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
- for (String name : teams) {
- TeamModel cloneTeam = getTeamModel(name);
- if (cloneTeam.canClone(repository)) {
- // origin team can clone origin, grant clone access to fork
- cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
- }
- cloneTeams.add(cloneTeam);
- }
- userService.updateTeamModels(cloneTeams);
-
- // add this clone to the cached model
- addToCachedRepositoryList(cloneModel);
- return cloneModel;
- }
-
- /**
- * Allow to understand if GitBlit supports and is configured to allow
- * cookie-based authentication.
- *
- * @return status of Cookie authentication enablement.
- */
- public boolean allowCookieAuthentication() {
- return GitBlit.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies();
- }
-}
+ }
+ }
+ }
+ }
+
+ /**
+ * Gitblit is being shutdown either because the servlet container is
+ * shutting down or because the servlet container is re-deploying Gitblit.
+ */
+ @Override
+ public void contextDestroyed(ServletContextEvent contextEvent) {
+ logger.info("Gitblit context destroyed by servlet container.");
+ scheduledExecutor.shutdownNow();
+ luceneExecutor.close();
+ gcExecutor.close();
+ if (fanoutService != null) {
+ fanoutService.stop();
+ }
+ if (gitDaemon != null) {
+ gitDaemon.stop();
+ }
+ }
+
+ /**
+ *
+ * @return true if we are running the gc executor
+ */
+ public boolean isCollectingGarbage() {
+ return gcExecutor.isRunning();
+ }
+
+ /**
+ * Returns true if Gitblit is actively collecting garbage in this repository.
+ *
+ * @param repositoryName
+ * @return true if actively collecting garbage
+ */
+ public boolean isCollectingGarbage(String repositoryName) {
+ return gcExecutor.isCollectingGarbage(repositoryName);
+ }
+
+ /**
+ * Creates a personal fork of the specified repository. The clone is view
+ * restricted by default and the owner of the source repository is given
+ * access to the clone.
+ *
+ * @param repository
+ * @param user
+ * @return the repository model of the fork, if successful
+ * @throws GitBlitException
+ */
+ public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
+ String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
+ String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name);
+
+ // clone the repository
+ try {
+ JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null);
+ } catch (Exception e) {
+ throw new GitBlitException(e);
+ }
+
+ // create a Gitblit repository model for the clone
+ RepositoryModel cloneModel = repository.cloneAs(cloneName);
+ // owner has REWIND/RW+ permissions
+ cloneModel.addOwner(user.username);
+ updateRepositoryModel(cloneName, cloneModel, false);
+
+ // add the owner of the source repository to the clone's access list
+ if (!ArrayUtils.isEmpty(repository.owners)) {
+ for (String owner : repository.owners) {
+ UserModel originOwner = getUserModel(owner);
+ if (originOwner != null) {
+ originOwner.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ updateUserModel(originOwner.username, originOwner, false);
+ }
+ }
+ }
+
+ // grant origin's user list clone permission to fork
+ List<String> users = getRepositoryUsers(repository);
+ List<UserModel> cloneUsers = new ArrayList<UserModel>();
+ for (String name : users) {
+ if (!name.equalsIgnoreCase(user.username)) {
+ UserModel cloneUser = getUserModel(name);
+ if (cloneUser.canClone(repository)) {
+ // origin user can clone origin, grant clone access to fork
+ cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ }
+ cloneUsers.add(cloneUser);
+ }
+ }
+ userService.updateUserModels(cloneUsers);
+
+ // grant origin's team list clone permission to fork
+ List<String> teams = getRepositoryTeams(repository);
+ List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
+ for (String name : teams) {
+ TeamModel cloneTeam = getTeamModel(name);
+ if (cloneTeam.canClone(repository)) {
+ // origin team can clone origin, grant clone access to fork
+ cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
+ }
+ cloneTeams.add(cloneTeam);
+ }
+ userService.updateTeamModels(cloneTeams);
+
+ // add this clone to the cached model
+ addToCachedRepositoryList(cloneModel);
+ return cloneModel;
+ }
+
+ /**
+ * Allow to understand if GitBlit supports and is configured to allow
+ * cookie-based authentication.
+ *
+ * @return status of Cookie authentication enablement.
+ */
+ public boolean allowCookieAuthentication() {
+ return GitBlit.getBoolean(Keys.web.allowCookieAuthentication, true) && userService.supportsCookies();
+ }
+}
diff --git a/src/main/java/com/gitblit/IStoredSettings.java b/src/main/java/com/gitblit/IStoredSettings.java
index 246262db..f0b1e632 100644
--- a/src/main/java/com/gitblit/IStoredSettings.java
+++ b/src/main/java/com/gitblit/IStoredSettings.java
@@ -161,7 +161,7 @@ public abstract class IStoredSettings {
/**
* Returns an long filesize from a string value such as 50m or 50mb
- * @param name
+ * @param n
* @param defaultValue
* @return a long filesize or defaultValue if the key does not exist or can
* not be parsed
diff --git a/src/main/java/com/gitblit/models/UserModel.java b/src/main/java/com/gitblit/models/UserModel.java
index e4c659c1..b480b885 100644
--- a/src/main/java/com/gitblit/models/UserModel.java
+++ b/src/main/java/com/gitblit/models/UserModel.java
@@ -75,17 +75,21 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
// non-persisted fields
public boolean isAuthenticated;
public AccountType accountType;
+
+ public UserPreferences userPreferences;
public UserModel(String username) {
this.username = username;
this.isAuthenticated = true;
this.accountType = AccountType.LOCAL;
+ this.userPreferences = new UserPreferences(this.username);
}
private UserModel() {
this.username = "$anonymous";
this.isAuthenticated = false;
this.accountType = AccountType.LOCAL;
+ this.userPreferences = new UserPreferences(this.username);
}
public boolean isLocalAccount() {
@@ -602,6 +606,10 @@ public class UserModel implements Principal, Serializable, Comparable<UserModel>
return "~" + username;
}
+ public UserPreferences getPreferences() {
+ return userPreferences;
+ }
+
@Override
public int hashCode() {
return username.hashCode();
diff --git a/src/main/java/com/gitblit/models/UserPreferences.java b/src/main/java/com/gitblit/models/UserPreferences.java
new file mode 100644
index 00000000..10f69cfe
--- /dev/null
+++ b/src/main/java/com/gitblit/models/UserPreferences.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * User preferences.
+ *
+ * @author James Moger
+ *
+ */
+public class UserPreferences implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public final String username;
+
+ private final Map<String, UserRepositoryPreferences> repositoryPreferences = new TreeMap<String, UserRepositoryPreferences>();
+
+ public UserPreferences(String username) {
+ this.username = username;
+ }
+
+ public Locale getLocale() {
+ if (StringUtils.isEmpty(locale)) {
+ return null;
+ }
+ return new Locale(locale);
+ }
+
+ public UserRepositoryPreferences getRepositoryPreferences(String repositoryName) {
+ String key = repositoryName.toLowerCase();
+ if (!repositoryPreferences.containsKey(key)) {
+ // default preferences
+ UserRepositoryPreferences prefs = new UserRepositoryPreferences();
+ prefs.username = username;
+ prefs.repositoryName = repositoryName;
+ repositoryPreferences.put(key, prefs);
+ }
+ return repositoryPreferences.get(key);
+ }
+
+ public void setRepositoryPreferences(UserRepositoryPreferences pref) {
+ repositoryPreferences.put(pref.repositoryName.toLowerCase(), pref);
+ }
+
+ public boolean isStarredRepository(String repository) {
+ if (repositoryPreferences == null) {
+ return false;
+ }
+ String key = repository.toLowerCase();
+ if (repositoryPreferences.containsKey(key)) {
+ UserRepositoryPreferences pref = repositoryPreferences.get(key);
+ return pref.starred;
+ }
+ return false;
+ }
+
+ public List<String> getStarredRepositories() {
+ List<String> list = new ArrayList<String>();
+ for (UserRepositoryPreferences prefs : repositoryPreferences.values()) {
+ if (prefs.starred) {
+ list.add(prefs.repositoryName);
+ }
+ }
+ Collections.sort(list);
+ return list;
+ }
+}
diff --git a/src/main/java/com/gitblit/models/UserRepositoryPreferences.java b/src/main/java/com/gitblit/models/UserRepositoryPreferences.java
new file mode 100644
index 00000000..bc3a1845
--- /dev/null
+++ b/src/main/java/com/gitblit/models/UserRepositoryPreferences.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.models;
+
+import java.io.Serializable;
+
+/**
+ * User repository preferences.
+ *
+ * @author James Moger
+ *
+ */
+public class UserRepositoryPreferences implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public String username;
+
+ public String repositoryName;
+
+ public boolean starred;
+
+ @Override
+ public String toString() {
+ return username + ":" + repositoryName;
+ }
+}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index eaf07c4c..1b8583e6 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -1,474 +1,476 @@
-gb.repository = repository
-gb.owner = owner
-gb.description = description
-gb.lastChange = last change
-gb.refs = refs
-gb.tag = tag
-gb.tags = tags
-gb.author = author
-gb.committer = committer
-gb.commit = commit
-gb.tree = tree
-gb.parent = parent
-gb.url = URL
-gb.history = history
-gb.raw = raw
-gb.object = object
-gb.ticketId = ticket id
-gb.ticketAssigned = assigned
-gb.ticketOpenDate = open date
-gb.ticketState = state
-gb.ticketComments = comments
-gb.view = view
-gb.local = local
-gb.remote = remote
-gb.branches = branches
-gb.patch = patch
-gb.diff = diff
-gb.log = log
-gb.moreLogs = more commits...
-gb.allTags = all tags...
-gb.allBranches = all branches...
-gb.summary = summary
-gb.ticket = ticket
-gb.newRepository = new repository
-gb.newUser = new user
-gb.commitdiff = commitdiff
-gb.tickets = tickets
-gb.pageFirst = first
-gb.pagePrevious prev
-gb.pageNext = next
-gb.head = HEAD
-gb.blame = blame
-gb.login = login
-gb.logout = logout
-gb.username = username
-gb.password = password
-gb.tagger = tagger
-gb.moreHistory = more history...
-gb.difftocurrent = diff to current
-gb.search = search
-gb.searchForAuthor = Search for commits authored by
-gb.searchForCommitter = Search for commits committed by
-gb.addition = addition
-gb.modification = modification
-gb.deletion = deletion
-gb.rename = rename
-gb.metrics = metrics
-gb.stats = stats
-gb.markdown = markdown
-gb.changedFiles = changed files
-gb.filesAdded = {0} files added
-gb.filesModified = {0} files modified
-gb.filesDeleted = {0} files deleted
-gb.filesCopied = {0} files copied
-gb.filesRenamed = {0} files renamed
-gb.missingUsername = Missing Username
-gb.edit = edit
-gb.searchTypeTooltip = Select Search Type
-gb.searchTooltip = Search {0}
-gb.delete = delete
-gb.docs = docs
-gb.accessRestriction = access restriction
-gb.name = name
-gb.enableTickets = enable tickets
-gb.enableDocs = enable docs
-gb.save = save
-gb.showRemoteBranches = show remote branches
-gb.editUsers = edit users
-gb.confirmPassword = confirm password
-gb.restrictedRepositories = restricted repositories
-gb.canAdmin = can admin
-gb.notRestricted = anonymous view, clone, & push
-gb.pushRestricted = authenticated push
-gb.cloneRestricted = authenticated clone & push
-gb.viewRestricted = authenticated view, clone, & push
-gb.useTicketsDescription = readonly, distributed Ticgit issues
-gb.useDocsDescription = enumerates Markdown documentation in repository
-gb.showRemoteBranchesDescription = show remote branches
-gb.canAdminDescription = can administer Gitblit server
-gb.permittedUsers = permitted users
-gb.isFrozen = is frozen
-gb.isFrozenDescription = deny push operations
-gb.zip = zip
-gb.showReadme = show readme
-gb.showReadmeDescription = show a \"readme\" Markdown file on the summary page
-gb.nameDescription = use '/' to group repositories. e.g. libraries/mycoollib.git
-gb.ownerDescription = the owner may edit repository settings
-gb.blob = blob
-gb.commitActivityTrend = commit activity trend
-gb.commitActivityDOW = commit activity by day of week
-gb.commitActivityAuthors = primary authors by commit activity
-gb.feed = feed
-gb.cancel = cancel
-gb.changePassword = change password
-gb.isFederated = is federated
-gb.federateThis = federate this repository
-gb.federateOrigin = federate the origin
-gb.excludeFromFederation = exclude from federation
-gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account
-gb.tokens = federation tokens
-gb.tokenAllDescription = all repositories, users, & settings
-gb.tokenUnrDescription = all repositories & users
-gb.tokenJurDescription = all repositories
-gb.federatedRepositoryDefinitions = repository definitions
-gb.federatedUserDefinitions = user definitions
-gb.federatedSettingDefinitions = setting definitions
-gb.proposals = federation proposals
-gb.received = received
-gb.type = type
-gb.token = token
-gb.repositories = repositories
-gb.proposal = proposal
-gb.frequency = frequency
-gb.folder = folder
-gb.lastPull = last pull
-gb.nextPull = next pull
-gb.inclusions = inclusions
-gb.exclusions = exclusions
-gb.registration = registration
-gb.registrations = federation registrations
-gb.sendProposal = propose
-gb.status = status
-gb.origin = origin
-gb.headRef = default branch (HEAD)
-gb.headRefDescription = change the ref that HEAD links to. e.g. refs/heads/master
-gb.federationStrategy = federation strategy
-gb.federationRegistration = federation registration
-gb.federationResults = federation pull results
-gb.federationSets = federation sets
-gb.message = message
-gb.myUrlDescription = the publicly accessible url for your Gitblit instance
-gb.destinationUrl = send to
-gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
-gb.users = users
-gb.federation = federation
-gb.error = error
-gb.refresh = refresh
-gb.browse = browse
-gb.clone = clone
-gb.filter = filter
-gb.create = create
-gb.servers = servers
-gb.recent = recent
-gb.available = available
-gb.selected = selected
-gb.size = size
-gb.downloading = downloading
-gb.loading = loading
-gb.starting = starting
-gb.general = general
-gb.settings = settings
-gb.manage = manage
-gb.lastLogin = last login
-gb.skipSizeCalculation = skip size calculation
-gb.skipSizeCalculationDescription = do not calculate the repository size (reduces page load time)
-gb.skipSummaryMetrics = skip summary metrics
-gb.skipSummaryMetricsDescription = do not calculate metrics on the summary page (reduces page load time)
-gb.accessLevel = access level
-gb.default = default
-gb.setDefault = set default
-gb.since = since
-gb.status = status
-gb.bootDate = boot date
-gb.servletContainer = servlet container
-gb.heapMaximum = maximum heap
-gb.heapAllocated = allocated heap
-gb.heapUsed = used heap
-gb.free = free
-gb.version = version
-gb.releaseDate = release date
-gb.date = date
-gb.activity = activity
-gb.subscribe = subscribe
-gb.branch = branch
-gb.maxHits = max hits
-gb.recentActivity = recent activity
-gb.recentActivityStats = last {0} days / {1} commits by {2} authors
-gb.recentActivityNone = last {0} days / none
-gb.dailyActivity = daily activity
-gb.activeRepositories = active repositories
-gb.activeAuthors = active authors
-gb.commits = commits
-gb.teams = teams
-gb.teamName = team name
-gb.teamMembers = team members
-gb.teamMemberships = team memberships
-gb.newTeam = new team
-gb.permittedTeams = permitted teams
-gb.emptyRepository = empty repository
-gb.repositoryUrl = repository url
-gb.mailingLists = mailing lists
-gb.preReceiveScripts = pre-receive scripts
-gb.postReceiveScripts = post-receive scripts
-gb.hookScripts = hook scripts
-gb.customFields = custom fields
-gb.customFieldsDescription = custom fields available to Groovy hooks
-gb.accessPermissions = access permissions
-gb.filters = filters
-gb.generalDescription = common settings
-gb.accessPermissionsDescription = restrict access by users and teams
-gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
-gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
-gb.federationRepositoryDescription = share this repository with other Gitblit servers
-gb.hookScriptsDescription = run Groovy scripts on pushes to this Gitblit server
-gb.reset = reset
-gb.pages = pages
-gb.workingCopy = working copy
-gb.workingCopyWarning = this repository has a working copy and can not receive pushes
-gb.query = query
-gb.queryHelp = Standard query syntax is supported.<p/><p/>Please see <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> for details.
-gb.queryResults = results {0} - {1} ({2} hits)
-gb.noHits = no hits
-gb.authored = authored
-gb.committed = committed
-gb.indexedBranches = indexed branches
-gb.indexedBranchesDescription = select the branches to include in your Lucene index
-gb.noIndexedRepositoriesWarning = none of your repositories are configured for Lucene indexing
-gb.undefinedQueryWarning = query is undefined!
-gb.noSelectedRepositoriesWarning = please select one or more repositories!
-gb.luceneDisabled = Lucene indexing is disabled
-gb.failedtoRead = Failed to read
-gb.isNotValidFile = is not a valid file
-gb.failedToReadMessage = Failed to read default message from {0}!
-gb.passwordsDoNotMatch = Passwords do not match!
-gb.passwordTooShort = Password is too short. Minimum length is {0} characters.
-gb.passwordChanged = Password successfully changed.
-gb.passwordChangeAborted = Password change aborted.
-gb.pleaseSetRepositoryName = Please set repository name!
-gb.illegalLeadingSlash = Leading root folder references (/) are prohibited.
-gb.illegalRelativeSlash = Relative folder references (../) are prohibited.
-gb.illegalCharacterRepositoryName = Illegal character ''{0}'' in repository name!
-gb.selectAccessRestriction = Please select access restriction!
-gb.selectFederationStrategy = Please select federation strategy!
-gb.pleaseSetTeamName = Please enter a teamname!
-gb.teamNameUnavailable = Team name ''{0}'' is unavailable.
-gb.teamMustSpecifyRepository = A team must specify at least one repository.
-gb.teamCreated = New team ''{0}'' successfully created.
-gb.pleaseSetUsername = Please enter a username!
-gb.usernameUnavailable = Username ''{0}'' is unavailable.
-gb.combinedMd5Rename = Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename.
-gb.userCreated = New user ''{0}'' successfully created.
-gb.couldNotFindFederationRegistration = Could not find federation registration!
-gb.failedToFindGravatarProfile = Failed to find Gravatar profile for {0}
-gb.branchStats = {0} commits and {1} tags in {2}
-gb.repositoryNotSpecified = Repository not specified!
-gb.repositoryNotSpecifiedFor = Repository not specified for {0}!
-gb.canNotLoadRepository = Can not load repository
-gb.commitIsNull = Commit is null
-gb.unauthorizedAccessForRepository = Unauthorized access for repository
-gb.failedToFindCommit = Failed to find commit \"{0}\" in {1} for {2} page!
-gb.couldNotFindFederationProposal = Could not find federation proposal!
-gb.invalidUsernameOrPassword = Invalid username or password!
-gb.OneProposalToReview = There is 1 federation proposal awaiting review.
-gb.nFederationProposalsToReview = There are {0} federation proposals awaiting review.
-gb.couldNotFindTag = Could not find tag {0}
-gb.couldNotCreateFederationProposal = Could not create federation proposal!
-gb.pleaseSetGitblitUrl = Please enter your Gitblit url!
-gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal!
-gb.proposalReceived = Proposal successfully received by {0}.
-gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
-gb.noProposals = Sorry, {0} is not accepting proposals at this time.
-gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
-gb.proposalFailed = Sorry, {0} did not receive any proposal data!
-gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
-gb.failedToSendProposal = Failed to send proposal!
-gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
-gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes!
-gb.displayName = display name
-gb.emailAddress = email address
-gb.errorAdminLoginRequired = Administration requires a login
-gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
-gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
-gb.errorAdministrationDisabled = Administration is disabled
-gb.lastNDays = last {0} days
-gb.completeGravatarProfile = Complete profile on Gravatar.com
-gb.none = none
-gb.line = line
-gb.content = content
-gb.empty = empty
-gb.inherited = inherited
-gb.deleteRepository = Delete repository \"{0}\"?
-gb.repositoryDeleted = Repository ''{0}'' deleted.
-gb.repositoryDeleteFailed = Failed to delete repository ''{0}''!
-gb.deleteUser = Delete user \"{0}\"?
-gb.userDeleted = User ''{0}'' deleted.
-gb.userDeleteFailed = Failed to delete user ''{0}''!
-gb.time.justNow = just now
-gb.time.today = today
-gb.time.yesterday = yesterday
-gb.time.minsAgo = {0} mins ago
-gb.time.hoursAgo = {0} hours ago
-gb.time.daysAgo = {0} days ago
-gb.time.weeksAgo = {0} weeks ago
-gb.time.monthsAgo = {0} months ago
-gb.time.oneYearAgo = 1 year ago
-gb.time.yearsAgo = {0} years ago
-gb.duration.oneDay = 1 day
-gb.duration.days = {0} days
-gb.duration.oneMonth = 1 month
-gb.duration.months = {0} months
-gb.duration.oneYear = 1 year
-gb.duration.years = {0} years
-gb.authorizationControl = authorization control
-gb.allowAuthenticatedDescription = grant RW+ permission to all authenticated users
-gb.allowNamedDescription = grant fine-grained permissions to named users or teams
-gb.markdownFailure = Failed to parse Markdown content!
-gb.clearCache = clear cache
-gb.projects = projects
-gb.project = project
-gb.allProjects = all projects
-gb.copyToClipboard = copy to clipboard
-gb.fork = fork
-gb.forks = forks
-gb.forkRepository = fork {0}?
-gb.repositoryForked = {0} has been forked
-gb.repositoryForkFailed= fork has failed
-gb.personalRepositories = personal repositories
-gb.allowForks = allow forks
-gb.allowForksDescription = allow authorized users to fork this repository
-gb.forkedFrom = forked from
-gb.canFork = can fork
-gb.canForkDescription = can fork authorized repositories to personal repositories
-gb.myFork = view my fork
-gb.forksProhibited = forks prohibited
-gb.forksProhibitedWarning = this repository forbids forks
-gb.noForks = {0} has no forks
-gb.forkNotAuthorized = sorry, you are not authorized to fork {0}
-gb.forkInProgress = fork in progress
-gb.preparingFork = preparing your fork...
-gb.isFork = is fork
-gb.canCreate = can create
-gb.canCreateDescription = can create personal repositories
-gb.illegalPersonalRepositoryLocation = your personal repository must be located at \"{0}\"
-gb.verifyCommitter = verify committer
-gb.verifyCommitterDescription = require committer identity to match pushing Gitblt user account
-gb.verifyCommitterNote = all merges require "--no-ff" to enforce committer identity
-gb.repositoryPermissions = repository permissions
-gb.userPermissions = user permissions
-gb.teamPermissions = team permissions
-gb.add = add
-gb.noPermission = DELETE THIS PERMISSION
-gb.excludePermission = {0} (exclude)
-gb.viewPermission = {0} (view)
-gb.clonePermission = {0} (clone)
-gb.pushPermission = {0} (push)
-gb.createPermission = {0} (push, ref creation)
-gb.deletePermission = {0} (push, ref creation+deletion)
-gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
-gb.permission = permission
-gb.regexPermission = this permission is set from regular expression \"{0}\"
-gb.accessDenied = access denied
-gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
-gb.gcPeriod = GC period
-gb.gcPeriodDescription = duration between garbage collections
-gb.gcThreshold = GC threshold
-gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
-gb.ownerPermission = repository owner
-gb.administrator = admin
-gb.administratorPermission = Gitblit administrator
-gb.team = team
-gb.teamPermission = permission set by \"{0}\" team membership
-gb.missing = missing!
-gb.missingPermission = the repository for this permission is missing!
-gb.mutable = mutable
-gb.specified = specified
-gb.effective = effective
-gb.organizationalUnit = organizational unit
-gb.organization = organization
-gb.locality = locality
-gb.stateProvince = state or province
-gb.countryCode = country code
-gb.properties = properties
-gb.issued = issued
-gb.expires = expires
-gb.expired = expired
-gb.expiring = expiring
-gb.revoked = revoked
-gb.serialNumber = serial number
-gb.certificates = certificates
-gb.newCertificate = new certificate
-gb.revokeCertificate = revoke certificate
-gb.sendEmail = send email
-gb.passwordHint = password hint
-gb.ok = ok
-gb.invalidExpirationDate = invalid expiration date!
-gb.passwordHintRequired = password hint required!
-gb.viewCertificate = view certificate
-gb.subject = subject
-gb.issuer = issuer
-gb.validFrom = valid from
-gb.validUntil = valid until
-gb.publicKey = public key
-gb.signatureAlgorithm = signature algorithm
-gb.sha1FingerPrint = SHA-1 Fingerprint
-gb.md5FingerPrint = MD5 Fingerprint
-gb.reason = reason
-gb.revokeCertificateReason = Please select a reason for certificate revocation
-gb.unspecified = unspecified
-gb.keyCompromise = key compromise
-gb.caCompromise = CA compromise
-gb.affiliationChanged = affiliation changed
-gb.superseded = superseded
-gb.cessationOfOperation = cessation of operation
-gb.privilegeWithdrawn = privilege withdrawn
-gb.time.inMinutes = in {0} mins
-gb.time.inHours = in {0} hours
-gb.time.inDays = in {0} days
-gb.hostname = hostname
-gb.hostnameRequired = Please enter a hostname
-gb.newSSLCertificate = new server SSL certificate
-gb.newCertificateDefaults = new certificate defaults
-gb.duration = duration
-gb.certificateRevoked = Certificate {0,number,0} has been revoked
-gb.clientCertificateGenerated = Successfully generated new client certificate for {0}
-gb.sslCertificateGenerated = Successfully generated new server SSL certificate for {0}
-gb.newClientCertificateMessage = NOTE:\nThe 'password' is not the user's password, it is the password to protect the user's keystore. This password is not saved so you must also enter a 'hint' which will be included in the user's README instructions.
-gb.certificate = certificate
-gb.emailCertificateBundle = email client certificate bundle
-gb.pleaseGenerateClientCertificate = Please generate a client certificate for {0}
-gb.clientCertificateBundleSent = Client certificate bundle for {0} sent
-gb.enterKeystorePassword = Please enter the Gitblit keystore password
-gb.warning = warning
-gb.jceWarning = Your Java Runtime Environment does not have the \"JCE Unlimited Strength Jurisdiction Policy\" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
-gb.maxActivityCommits = max activity commits
-gb.maxActivityCommitsDescription = maximum number of commits to contribute to the Activity page
-gb.noMaximum = no maximum
-gb.attributes = attributes
-gb.serveCertificate = serve https with this certificate
-gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certificate for {0}.\nYou must restart Gitblit to use the new certificate.\n\nIf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
-gb.validity = validity
-gb.siteName = site name
-gb.siteNameDescription = short, descriptive name of your server
-gb.excludeFromActivity = exclude from activity page
-gb.isSparkleshared = repository is Sparkleshared
-gb.owners = owners
-gb.sessionEnded = Session has been closed
-gb.closeBrowser = Please close the browser to properly end the session.
+gb.repository = repository
+gb.owner = owner
+gb.description = description
+gb.lastChange = last change
+gb.refs = refs
+gb.tag = tag
+gb.tags = tags
+gb.author = author
+gb.committer = committer
+gb.commit = commit
+gb.tree = tree
+gb.parent = parent
+gb.url = URL
+gb.history = history
+gb.raw = raw
+gb.object = object
+gb.ticketId = ticket id
+gb.ticketAssigned = assigned
+gb.ticketOpenDate = open date
+gb.ticketState = state
+gb.ticketComments = comments
+gb.view = view
+gb.local = local
+gb.remote = remote
+gb.branches = branches
+gb.patch = patch
+gb.diff = diff
+gb.log = log
+gb.moreLogs = more commits...
+gb.allTags = all tags...
+gb.allBranches = all branches...
+gb.summary = summary
+gb.ticket = ticket
+gb.newRepository = new repository
+gb.newUser = new user
+gb.commitdiff = commitdiff
+gb.tickets = tickets
+gb.pageFirst = first
+gb.pagePrevious prev
+gb.pageNext = next
+gb.head = HEAD
+gb.blame = blame
+gb.login = login
+gb.logout = logout
+gb.username = username
+gb.password = password
+gb.tagger = tagger
+gb.moreHistory = more history...
+gb.difftocurrent = diff to current
+gb.search = search
+gb.searchForAuthor = Search for commits authored by
+gb.searchForCommitter = Search for commits committed by
+gb.addition = addition
+gb.modification = modification
+gb.deletion = deletion
+gb.rename = rename
+gb.metrics = metrics
+gb.stats = stats
+gb.markdown = markdown
+gb.changedFiles = changed files
+gb.filesAdded = {0} files added
+gb.filesModified = {0} files modified
+gb.filesDeleted = {0} files deleted
+gb.filesCopied = {0} files copied
+gb.filesRenamed = {0} files renamed
+gb.missingUsername = Missing Username
+gb.edit = edit
+gb.searchTypeTooltip = Select Search Type
+gb.searchTooltip = Search {0}
+gb.delete = delete
+gb.docs = docs
+gb.accessRestriction = access restriction
+gb.name = name
+gb.enableTickets = enable tickets
+gb.enableDocs = enable docs
+gb.save = save
+gb.showRemoteBranches = show remote branches
+gb.editUsers = edit users
+gb.confirmPassword = confirm password
+gb.restrictedRepositories = restricted repositories
+gb.canAdmin = can admin
+gb.notRestricted = anonymous view, clone, & push
+gb.pushRestricted = authenticated push
+gb.cloneRestricted = authenticated clone & push
+gb.viewRestricted = authenticated view, clone, & push
+gb.useTicketsDescription = readonly, distributed Ticgit issues
+gb.useDocsDescription = enumerates Markdown documentation in repository
+gb.showRemoteBranchesDescription = show remote branches
+gb.canAdminDescription = can administer Gitblit server
+gb.permittedUsers = permitted users
+gb.isFrozen = is frozen
+gb.isFrozenDescription = deny push operations
+gb.zip = zip
+gb.showReadme = show readme
+gb.showReadmeDescription = show a \"readme\" Markdown file on the summary page
+gb.nameDescription = use '/' to group repositories. e.g. libraries/mycoollib.git
+gb.ownerDescription = the owner may edit repository settings
+gb.blob = blob
+gb.commitActivityTrend = commit activity trend
+gb.commitActivityDOW = commit activity by day of week
+gb.commitActivityAuthors = primary authors by commit activity
+gb.feed = feed
+gb.cancel = cancel
+gb.changePassword = change password
+gb.isFederated = is federated
+gb.federateThis = federate this repository
+gb.federateOrigin = federate the origin
+gb.excludeFromFederation = exclude from federation
+gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account
+gb.tokens = federation tokens
+gb.tokenAllDescription = all repositories, users, & settings
+gb.tokenUnrDescription = all repositories & users
+gb.tokenJurDescription = all repositories
+gb.federatedRepositoryDefinitions = repository definitions
+gb.federatedUserDefinitions = user definitions
+gb.federatedSettingDefinitions = setting definitions
+gb.proposals = federation proposals
+gb.received = received
+gb.type = type
+gb.token = token
+gb.repositories = repositories
+gb.proposal = proposal
+gb.frequency = frequency
+gb.folder = folder
+gb.lastPull = last pull
+gb.nextPull = next pull
+gb.inclusions = inclusions
+gb.exclusions = exclusions
+gb.registration = registration
+gb.registrations = federation registrations
+gb.sendProposal = propose
+gb.status = status
+gb.origin = origin
+gb.headRef = default branch (HEAD)
+gb.headRefDescription = change the ref that HEAD links to. e.g. refs/heads/master
+gb.federationStrategy = federation strategy
+gb.federationRegistration = federation registration
+gb.federationResults = federation pull results
+gb.federationSets = federation sets
+gb.message = message
+gb.myUrlDescription = the publicly accessible url for your Gitblit instance
+gb.destinationUrl = send to
+gb.destinationUrlDescription = the url of the Gitblit instance to send your proposal
+gb.users = users
+gb.federation = federation
+gb.error = error
+gb.refresh = refresh
+gb.browse = browse
+gb.clone = clone
+gb.filter = filter
+gb.create = create
+gb.servers = servers
+gb.recent = recent
+gb.available = available
+gb.selected = selected
+gb.size = size
+gb.downloading = downloading
+gb.loading = loading
+gb.starting = starting
+gb.general = general
+gb.settings = settings
+gb.manage = manage
+gb.lastLogin = last login
+gb.skipSizeCalculation = skip size calculation
+gb.skipSizeCalculationDescription = do not calculate the repository size (reduces page load time)
+gb.skipSummaryMetrics = skip summary metrics
+gb.skipSummaryMetricsDescription = do not calculate metrics on the summary page (reduces page load time)
+gb.accessLevel = access level
+gb.default = default
+gb.setDefault = set default
+gb.since = since
+gb.status = status
+gb.bootDate = boot date
+gb.servletContainer = servlet container
+gb.heapMaximum = maximum heap
+gb.heapAllocated = allocated heap
+gb.heapUsed = used heap
+gb.free = free
+gb.version = version
+gb.releaseDate = release date
+gb.date = date
+gb.activity = activity
+gb.subscribe = subscribe
+gb.branch = branch
+gb.maxHits = max hits
+gb.recentActivity = recent activity
+gb.recentActivityStats = last {0} days / {1} commits by {2} authors
+gb.recentActivityNone = last {0} days / none
+gb.dailyActivity = daily activity
+gb.activeRepositories = active repositories
+gb.activeAuthors = active authors
+gb.commits = commits
+gb.teams = teams
+gb.teamName = team name
+gb.teamMembers = team members
+gb.teamMemberships = team memberships
+gb.newTeam = new team
+gb.permittedTeams = permitted teams
+gb.emptyRepository = empty repository
+gb.repositoryUrl = repository url
+gb.mailingLists = mailing lists
+gb.preReceiveScripts = pre-receive scripts
+gb.postReceiveScripts = post-receive scripts
+gb.hookScripts = hook scripts
+gb.customFields = custom fields
+gb.customFieldsDescription = custom fields available to Groovy hooks
+gb.accessPermissions = access permissions
+gb.filters = filters
+gb.generalDescription = common settings
+gb.accessPermissionsDescription = restrict access by users and teams
+gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
+gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
+gb.federationRepositoryDescription = share this repository with other Gitblit servers
+gb.hookScriptsDescription = run Groovy scripts on pushes to this Gitblit server
+gb.reset = reset
+gb.pages = pages
+gb.workingCopy = working copy
+gb.workingCopyWarning = this repository has a working copy and can not receive pushes
+gb.query = query
+gb.queryHelp = Standard query syntax is supported.<p/><p/>Please see <a target="_new" href="http://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a> for details.
+gb.queryResults = results {0} - {1} ({2} hits)
+gb.noHits = no hits
+gb.authored = authored
+gb.committed = committed
+gb.indexedBranches = indexed branches
+gb.indexedBranchesDescription = select the branches to include in your Lucene index
+gb.noIndexedRepositoriesWarning = none of your repositories are configured for Lucene indexing
+gb.undefinedQueryWarning = query is undefined!
+gb.noSelectedRepositoriesWarning = please select one or more repositories!
+gb.luceneDisabled = Lucene indexing is disabled
+gb.failedtoRead = Failed to read
+gb.isNotValidFile = is not a valid file
+gb.failedToReadMessage = Failed to read default message from {0}!
+gb.passwordsDoNotMatch = Passwords do not match!
+gb.passwordTooShort = Password is too short. Minimum length is {0} characters.
+gb.passwordChanged = Password successfully changed.
+gb.passwordChangeAborted = Password change aborted.
+gb.pleaseSetRepositoryName = Please set repository name!
+gb.illegalLeadingSlash = Leading root folder references (/) are prohibited.
+gb.illegalRelativeSlash = Relative folder references (../) are prohibited.
+gb.illegalCharacterRepositoryName = Illegal character ''{0}'' in repository name!
+gb.selectAccessRestriction = Please select access restriction!
+gb.selectFederationStrategy = Please select federation strategy!
+gb.pleaseSetTeamName = Please enter a teamname!
+gb.teamNameUnavailable = Team name ''{0}'' is unavailable.
+gb.teamMustSpecifyRepository = A team must specify at least one repository.
+gb.teamCreated = New team ''{0}'' successfully created.
+gb.pleaseSetUsername = Please enter a username!
+gb.usernameUnavailable = Username ''{0}'' is unavailable.
+gb.combinedMd5Rename = Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename.
+gb.userCreated = New user ''{0}'' successfully created.
+gb.couldNotFindFederationRegistration = Could not find federation registration!
+gb.failedToFindGravatarProfile = Failed to find Gravatar profile for {0}
+gb.branchStats = {0} commits and {1} tags in {2}
+gb.repositoryNotSpecified = Repository not specified!
+gb.repositoryNotSpecifiedFor = Repository not specified for {0}!
+gb.canNotLoadRepository = Can not load repository
+gb.commitIsNull = Commit is null
+gb.unauthorizedAccessForRepository = Unauthorized access for repository
+gb.failedToFindCommit = Failed to find commit \"{0}\" in {1} for {2} page!
+gb.couldNotFindFederationProposal = Could not find federation proposal!
+gb.invalidUsernameOrPassword = Invalid username or password!
+gb.OneProposalToReview = There is 1 federation proposal awaiting review.
+gb.nFederationProposalsToReview = There are {0} federation proposals awaiting review.
+gb.couldNotFindTag = Could not find tag {0}
+gb.couldNotCreateFederationProposal = Could not create federation proposal!
+gb.pleaseSetGitblitUrl = Please enter your Gitblit url!
+gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal!
+gb.proposalReceived = Proposal successfully received by {0}.
+gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
+gb.noProposals = Sorry, {0} is not accepting proposals at this time.
+gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
+gb.proposalFailed = Sorry, {0} did not receive any proposal data!
+gb.proposalError = Sorry, {0} reports that an unexpected error occurred!
+gb.failedToSendProposal = Failed to send proposal!
+gb.userServiceDoesNotPermitAddUser = {0} does not permit adding a user account!
+gb.userServiceDoesNotPermitPasswordChanges = {0} does not permit password changes!
+gb.displayName = display name
+gb.emailAddress = email address
+gb.errorAdminLoginRequired = Administration requires a login
+gb.errorOnlyAdminMayCreateRepository = Only an administrator may create a repository
+gb.errorOnlyAdminOrOwnerMayEditRepository = Only an administrator or the owner may edit a repository
+gb.errorAdministrationDisabled = Administration is disabled
+gb.lastNDays = last {0} days
+gb.completeGravatarProfile = Complete profile on Gravatar.com
+gb.none = none
+gb.line = line
+gb.content = content
+gb.empty = empty
+gb.inherited = inherited
+gb.deleteRepository = Delete repository \"{0}\"?
+gb.repositoryDeleted = Repository ''{0}'' deleted.
+gb.repositoryDeleteFailed = Failed to delete repository ''{0}''!
+gb.deleteUser = Delete user \"{0}\"?
+gb.userDeleted = User ''{0}'' deleted.
+gb.userDeleteFailed = Failed to delete user ''{0}''!
+gb.time.justNow = just now
+gb.time.today = today
+gb.time.yesterday = yesterday
+gb.time.minsAgo = {0} mins ago
+gb.time.hoursAgo = {0} hours ago
+gb.time.daysAgo = {0} days ago
+gb.time.weeksAgo = {0} weeks ago
+gb.time.monthsAgo = {0} months ago
+gb.time.oneYearAgo = 1 year ago
+gb.time.yearsAgo = {0} years ago
+gb.duration.oneDay = 1 day
+gb.duration.days = {0} days
+gb.duration.oneMonth = 1 month
+gb.duration.months = {0} months
+gb.duration.oneYear = 1 year
+gb.duration.years = {0} years
+gb.authorizationControl = authorization control
+gb.allowAuthenticatedDescription = grant RW+ permission to all authenticated users
+gb.allowNamedDescription = grant fine-grained permissions to named users or teams
+gb.markdownFailure = Failed to parse Markdown content!
+gb.clearCache = clear cache
+gb.projects = projects
+gb.project = project
+gb.allProjects = all projects
+gb.copyToClipboard = copy to clipboard
+gb.fork = fork
+gb.forks = forks
+gb.forkRepository = fork {0}?
+gb.repositoryForked = {0} has been forked
+gb.repositoryForkFailed= fork has failed
+gb.personalRepositories = personal repositories
+gb.allowForks = allow forks
+gb.allowForksDescription = allow authorized users to fork this repository
+gb.forkedFrom = forked from
+gb.canFork = can fork
+gb.canForkDescription = can fork authorized repositories to personal repositories
+gb.myFork = view my fork
+gb.forksProhibited = forks prohibited
+gb.forksProhibitedWarning = this repository forbids forks
+gb.noForks = {0} has no forks
+gb.forkNotAuthorized = sorry, you are not authorized to fork {0}
+gb.forkInProgress = fork in progress
+gb.preparingFork = preparing your fork...
+gb.isFork = is fork
+gb.canCreate = can create
+gb.canCreateDescription = can create personal repositories
+gb.illegalPersonalRepositoryLocation = your personal repository must be located at \"{0}\"
+gb.verifyCommitter = verify committer
+gb.verifyCommitterDescription = require committer identity to match pushing Gitblt user account
+gb.verifyCommitterNote = all merges require "--no-ff" to enforce committer identity
+gb.repositoryPermissions = repository permissions
+gb.userPermissions = user permissions
+gb.teamPermissions = team permissions
+gb.add = add
+gb.noPermission = DELETE THIS PERMISSION
+gb.excludePermission = {0} (exclude)
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.permission = permission
+gb.regexPermission = this permission is set from regular expression \"{0}\"
+gb.accessDenied = access denied
+gb.busyCollectingGarbage = sorry, Gitblit is busy collecting garbage in {0}
+gb.gcPeriod = GC period
+gb.gcPeriodDescription = duration between garbage collections
+gb.gcThreshold = GC threshold
+gb.gcThresholdDescription = minimum total size of loose objects to trigger early garbage collection
+gb.ownerPermission = repository owner
+gb.administrator = admin
+gb.administratorPermission = Gitblit administrator
+gb.team = team
+gb.teamPermission = permission set by \"{0}\" team membership
+gb.missing = missing!
+gb.missingPermission = the repository for this permission is missing!
+gb.mutable = mutable
+gb.specified = specified
+gb.effective = effective
+gb.organizationalUnit = organizational unit
+gb.organization = organization
+gb.locality = locality
+gb.stateProvince = state or province
+gb.countryCode = country code
+gb.properties = properties
+gb.issued = issued
+gb.expires = expires
+gb.expired = expired
+gb.expiring = expiring
+gb.revoked = revoked
+gb.serialNumber = serial number
+gb.certificates = certificates
+gb.newCertificate = new certificate
+gb.revokeCertificate = revoke certificate
+gb.sendEmail = send email
+gb.passwordHint = password hint
+gb.ok = ok
+gb.invalidExpirationDate = invalid expiration date!
+gb.passwordHintRequired = password hint required!
+gb.viewCertificate = view certificate
+gb.subject = subject
+gb.issuer = issuer
+gb.validFrom = valid from
+gb.validUntil = valid until
+gb.publicKey = public key
+gb.signatureAlgorithm = signature algorithm
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.md5FingerPrint = MD5 Fingerprint
+gb.reason = reason
+gb.revokeCertificateReason = Please select a reason for certificate revocation
+gb.unspecified = unspecified
+gb.keyCompromise = key compromise
+gb.caCompromise = CA compromise
+gb.affiliationChanged = affiliation changed
+gb.superseded = superseded
+gb.cessationOfOperation = cessation of operation
+gb.privilegeWithdrawn = privilege withdrawn
+gb.time.inMinutes = in {0} mins
+gb.time.inHours = in {0} hours
+gb.time.inDays = in {0} days
+gb.hostname = hostname
+gb.hostnameRequired = Please enter a hostname
+gb.newSSLCertificate = new server SSL certificate
+gb.newCertificateDefaults = new certificate defaults
+gb.duration = duration
+gb.certificateRevoked = Certificate {0,number,0} has been revoked
+gb.clientCertificateGenerated = Successfully generated new client certificate for {0}
+gb.sslCertificateGenerated = Successfully generated new server SSL certificate for {0}
+gb.newClientCertificateMessage = NOTE:\nThe 'password' is not the user's password, it is the password to protect the user's keystore. This password is not saved so you must also enter a 'hint' which will be included in the user's README instructions.
+gb.certificate = certificate
+gb.emailCertificateBundle = email client certificate bundle
+gb.pleaseGenerateClientCertificate = Please generate a client certificate for {0}
+gb.clientCertificateBundleSent = Client certificate bundle for {0} sent
+gb.enterKeystorePassword = Please enter the Gitblit keystore password
+gb.warning = warning
+gb.jceWarning = Your Java Runtime Environment does not have the \"JCE Unlimited Strength Jurisdiction Policy\" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
+gb.maxActivityCommits = max activity commits
+gb.maxActivityCommitsDescription = maximum number of commits to contribute to the Activity page
+gb.noMaximum = no maximum
+gb.attributes = attributes
+gb.serveCertificate = serve https with this certificate
+gb.sslCertificateGeneratedRestart = Successfully generated new server SSL certificate for {0}.\nYou must restart Gitblit to use the new certificate.\n\nIf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
+gb.validity = validity
+gb.siteName = site name
+gb.siteNameDescription = short, descriptive name of your server
+gb.excludeFromActivity = exclude from activity page
+gb.isSparkleshared = repository is Sparkleshared
+gb.owners = owners
+gb.sessionEnded = Session has been closed
+gb.closeBrowser = Please close the browser to properly end the session.
gb.doesNotExistInTree = {0} does not exist in tree {1}
-gb.enableIncrementalPushTags = enable incremental push tags
-gb.useIncrementalPushTagsDescription = on push, automatically tag each branch tip with an incremental revision number
-gb.incrementalPushTagMessage = Auto-tagged [{0}] branch on push
-gb.externalPermissions = {0} access permissions are externally maintained
-gb.viewAccess = You do not have Gitblit read or write access
-gb.overview = overview
-gb.home = home
-gb.monthlyActivity = monthly activity
-gb.myProfile = my profile
-gb.compare = compare
-gb.manual = manual
-gb.from = from
-gb.to = to
-gb.at = at
-gb.morePushes = all pushes...
-gb.pushes = pushes
-gb.pushedNCommitsTo = pushed {0} commits to
-gb.pushedOneCommitTo = pushed 1 commit to
-gb.viewComparison = view comparison of these {0} commits \u00bb
-gb.nMoreCommits = {0} more commits \u00bb
-gb.oneMoreCommit = 1 more commit \u00bb
-gb.pushedNewTag = pushed new tag
-gb.deletedTag = deleted tag
-gb.pushedNewBranch = pushed new branch
-gb.deletedBranch = deleted branch
-gb.rewind = REWIND \ No newline at end of file
+gb.enableIncrementalPushTags = enable incremental push tags
+gb.useIncrementalPushTagsDescription = on push, automatically tag each branch tip with an incremental revision number
+gb.incrementalPushTagMessage = Auto-tagged [{0}] branch on push
+gb.externalPermissions = {0} access permissions are externally maintained
+gb.viewAccess = You do not have Gitblit read or write access
+gb.overview = overview
+gb.home = home
+gb.monthlyActivity = monthly activity
+gb.myProfile = my profile
+gb.compare = compare
+gb.manual = manual
+gb.from = from
+gb.to = to
+gb.at = at
+gb.morePushes = all pushes...
+gb.pushes = pushes
+gb.pushedNCommitsTo = pushed {0} commits to
+gb.pushedOneCommitTo = pushed 1 commit to
+gb.viewComparison = view comparison of these {0} commits \u00bb
+gb.nMoreCommits = {0} more commits \u00bb
+gb.oneMoreCommit = 1 more commit \u00bb
+gb.pushedNewTag = pushed new tag
+gb.deletedTag = deleted tag
+gb.pushedNewBranch = pushed new branch
+gb.deletedBranch = deleted branch
+gb.rewind = REWIND
+gb.star = star
+gb.unstar = unstar \ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
index 7ac950cb..86e8bbe4 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
@@ -38,8 +38,9 @@
<div>
<div class="hidden-phone btn-group pull-right" style="margin-top:5px;">
<!-- future spot for other repo buttons -->
-<!-- <a class="btn"><i class="icon-star-empty" style="padding-right:3px;"></i>star</a> -->
-<!-- <a class="btn"><i class="icon-envelope" style="padding-right:3px;"></i>subscribe</a> -->
+ <a class="btn" wicket:id="starLink"></a>
+ <a class="btn" wicket:id="unstarLink"></a>
+
<a class="btn" wicket:id="myForkLink"><img style="border:0px;vertical-align:middle;" src="fork-black_16x16.png"></img> <wicket:message key="gb.myFork"></wicket:message></a>
<a class="btn" wicket:id="forkLink"><img style="border:0px;vertical-align:middle;" src="fork-black_16x16.png"></img> <wicket:message key="gb.fork"></wicket:message></a>
</div>
@@ -54,6 +55,10 @@
<wicket:child />
</div>
+ <wicket:fragment wicket:id="toolbarLinkFragment">
+ <i wicket:id="icon" style="padding-right:3px;"></i><span wicket:id="label"></span>
+ </wicket:fragment>
+
<wicket:fragment wicket:id="originFragment">
<p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index 94adf564..918127b3 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -28,6 +28,7 @@ import java.util.Set;
import org.apache.wicket.Component;
import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.TextField;
@@ -41,9 +42,12 @@ import org.eclipse.jgit.diff.DiffEntry.ChangeType;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.GitBlit;
+import com.gitblit.GitBlitException;
import com.gitblit.Keys;
import com.gitblit.PagesServlet;
import com.gitblit.SyndicationServlet;
@@ -52,7 +56,9 @@ import com.gitblit.models.RefModel;
import com.gitblit.models.RepositoryModel;
import com.gitblit.models.SubmoduleModel;
import com.gitblit.models.UserModel;
+import com.gitblit.models.UserRepositoryPreferences;
import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.DeepCopier;
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.PushLogUtils;
import com.gitblit.utils.StringUtils;
@@ -67,7 +73,11 @@ import com.gitblit.wicket.panels.NavigationPanel;
import com.gitblit.wicket.panels.RefsPanel;
public abstract class RepositoryPage extends RootPage {
+
+ private final Logger logger = LoggerFactory.getLogger(getClass());
+ private final String PARAM_STAR = "star";
+
protected final String projectName;
protected final String repositoryName;
protected final String objectId;
@@ -104,7 +114,7 @@ public abstract class RepositoryPage extends RootPage {
if (getRepositoryModel().isCollectingGarbage) {
error(MessageFormat.format(getString("gb.busyCollectingGarbage"), getRepositoryModel().name), true);
}
-
+
if (objectId != null) {
RefModel branch = null;
if ((branch = JGitUtils.getBranch(getRepository(), objectId)) != null) {
@@ -120,6 +130,22 @@ public abstract class RepositoryPage extends RootPage {
}
}
}
+
+ if (params.containsKey(PARAM_STAR)) {
+ // set starred state
+ boolean star = params.getBoolean(PARAM_STAR);
+ UserModel user = GitBlitWebSession.get().getUser();
+ if (user != null && user.isAuthenticated) {
+ UserRepositoryPreferences prefs = user.getPreferences().getRepositoryPreferences(getRepositoryModel().name);
+ prefs.starred = star;
+ try {
+ GitBlit.self().updateUserModel(user.username, user, false);
+ } catch (GitBlitException e) {
+ logger.error("Failed to update user " + user.username, e);
+ error(getString("gb.failedToUpdateUser"), false);
+ }
+ }
+ }
// register the available page links for this page and user
registeredPages = registerPages();
@@ -260,6 +286,26 @@ public abstract class RepositoryPage extends RootPage {
}
}
+ // (un)star link allows a user to star someone else's repository
+ if (user.isAuthenticated && !model.isOwner(user.username) && !model.isUsersPersonalRepository(user.username)) {
+ PageParameters starParams = DeepCopier.copy(getPageParameters());
+ starParams.put(PARAM_STAR, !user.getPreferences().isStarredRepository(model.name));
+ String toggleStarUrl = getRequestCycle().urlFor(getClass(), starParams).toString();
+ if (user.getPreferences().isStarredRepository(model.name)) {
+ // show unstar button
+ add(new Label("starLink").setVisible(false));
+ addToolbarButton("unstarLink", "icon-star-empty", getString("gb.unstar"), toggleStarUrl);
+ } else {
+ // show star button
+ addToolbarButton("starLink", "icon-star", getString("gb.star"), toggleStarUrl);
+ add(new Label("unstarLink").setVisible(false));
+ }
+ } else {
+ // anonymous user or the repository owner is browsing the repository
+ add(new Label("starLink").setVisible(false));
+ add(new Label("unstarLink").setVisible(false));
+ }
+
// fork controls
if (!allowForkControls() || user == null || !user.isAuthenticated) {
// must be logged-in to fork, hide all fork controls
@@ -292,6 +338,16 @@ public abstract class RepositoryPage extends RootPage {
super.setupPage(repositoryName, pageName);
}
+
+ protected void addToolbarButton(String wicketId, String iconClass, String label, String url) {
+ Fragment button = new Fragment(wicketId, "toolbarLinkFragment", this);
+ Label icon = new Label("icon");
+ WicketUtils.setCssClass(icon, iconClass);
+ button.add(icon);
+ button.add(new Label("label", label));
+ button.add(new SimpleAttributeModifier("href", url));
+ add(button);
+ }
protected void addSyndicationDiscoveryLink() {
add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(repositoryName,