From f76fee63ed9cb3a30d3c0c092d860b1cb93a481b Mon Sep 17 00:00:00 2001
From: Gerard Smyth <gerard.smyth@gmail.com>
Date: Thu, 08 May 2014 13:09:30 -0400
Subject: [PATCH] Updated the SyndicationServlet to provide an additional option to return details of the tags in the repository instead of the commits. This uses a new 'ot' request parameter to indicate the object type of the content to return, which can be ither TAG or COMMIT. If this is not provided, then COMMIT is assumed to maintain backwards compatability. If tags are returned, then the paging parameters, 'l' and 'pg' are still supported, but searching options are currently ignored.

---
 src/main/java/com/gitblit/GitBlit.java | 1158 +++++++++++++--------------------------------------------
 1 files changed, 264 insertions(+), 894 deletions(-)

diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index d736c57..3db5f08 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -15,273 +15,136 @@
  */
 package com.gitblit;
 
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.lang.reflect.Type;
 import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Date;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.TimeZone;
+import java.util.Set;
 
+import javax.inject.Singleton;
 import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
-import com.gitblit.Constants.FederationRequest;
-import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.Transport;
+import com.gitblit.manager.GitblitManager;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.manager.IUserManager;
 import com.gitblit.manager.ServicesManager;
-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.HttpUtils;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.JsonUtils;
-import com.gitblit.utils.ObjectCache;
+import com.gitblit.tickets.BranchTicketService;
+import com.gitblit.tickets.FileTicketService;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.NullTicketService;
+import com.gitblit.tickets.RedisTicketService;
+import com.gitblit.transport.ssh.IPublicKeyManager;
 import com.gitblit.utils.StringUtils;
-import com.google.gson.Gson;
-import com.google.gson.JsonIOException;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.reflect.TypeToken;
+
+import dagger.Module;
+import dagger.ObjectGraph;
+import dagger.Provides;
 
 /**
- * GitBlit is an aggregate interface delegate.  It implements all the manager
- * interfaces and delegates all methods calls to the actual manager implementations.
- * It's primary purpose is to provide complete management control to the git
- * upload and receive pack functions.
+ * GitBlit is the aggregate manager for the Gitblit webapp.  It provides all
+ * management functions and also manages some long-running services.
  *
  * @author James Moger
  *
  */
-public class GitBlit implements IGitblit {
+public class GitBlit extends GitblitManager {
 
-	private final Logger logger = LoggerFactory.getLogger(getClass());
-
-	private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
-
-	private final IStoredSettings settings;
-
-	private final IRuntimeManager runtimeManager;
-
-	private final INotificationManager notificationManager;
-
-	private final IUserManager userManager;
-
-	private final IAuthenticationManager authenticationManager;
-
-	private final IRepositoryManager repositoryManager;
-
-	private final IProjectManager projectManager;
-
-	private final IFederationManager federationManager;
+	private final ObjectGraph injector;
 
 	private final ServicesManager servicesManager;
 
+	private ITicketService ticketService;
+
 	public GitBlit(
 			IRuntimeManager runtimeManager,
+			IPluginManager pluginManager,
 			INotificationManager notificationManager,
 			IUserManager userManager,
 			IAuthenticationManager authenticationManager,
+			IPublicKeyManager publicKeyManager,
 			IRepositoryManager repositoryManager,
 			IProjectManager projectManager,
 			IFederationManager federationManager) {
 
-		this.settings = runtimeManager.getSettings();
-		this.runtimeManager = runtimeManager;
-		this.notificationManager = notificationManager;
-		this.userManager = userManager;
-		this.authenticationManager = authenticationManager;
-		this.repositoryManager = repositoryManager;
-		this.projectManager = projectManager;
-		this.federationManager = federationManager;
+		super(runtimeManager,
+				pluginManager,
+				notificationManager,
+				userManager,
+				authenticationManager,
+				publicKeyManager,
+				repositoryManager,
+				projectManager,
+				federationManager);
+
+		this.injector = ObjectGraph.create(getModules());
 
 		this.servicesManager = new ServicesManager(this);
 	}
 
 	@Override
 	public GitBlit start() {
-		loadSettingModels(runtimeManager.getSettingsModel());
+		super.start();
 		logger.info("Starting services manager...");
 		servicesManager.start();
+		configureTicketService();
 		return this;
 	}
 
 	@Override
 	public GitBlit stop() {
+		super.stop();
 		servicesManager.stop();
+		ticketService.stop();
 		return this;
 	}
 
-	/*
-	 * IGITBLIT
-	 */
-
-	/**
-	 * 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
-	 */
 	@Override
-	public RepositoryModel fork(RepositoryModel repository, UserModel user) throws GitBlitException {
-		String cloneName = MessageFormat.format("{0}/{1}.git", user.getPersonalPath(), StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name)));
-		String fromUrl = MessageFormat.format("file://{0}/{1}", repositoryManager.getRepositoriesFolder().getAbsolutePath(), repository.name);
-
-		// clone the repository
-		try {
-			JGitUtils.cloneRepository(repositoryManager.getRepositoriesFolder(), 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);
-		repositoryManager.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 = userManager.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 = repositoryManager.getRepositoryUsers(repository);
-		List<UserModel> cloneUsers = new ArrayList<UserModel>();
-		for (String name : users) {
-			if (!name.equalsIgnoreCase(user.username)) {
-				UserModel cloneUser = userManager.getUserModel(name);
-				if (cloneUser.canClone(repository)) {
-					// origin user can clone origin, grant clone access to fork
-					cloneUser.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-				}
-				cloneUsers.add(cloneUser);
-			}
-		}
-		userManager.updateUserModels(cloneUsers);
-
-		// grant origin's team list clone permission to fork
-		List<String> teams = repositoryManager.getRepositoryTeams(repository);
-		List<TeamModel> cloneTeams = new ArrayList<TeamModel>();
-		for (String name : teams) {
-			TeamModel cloneTeam = userManager.getTeamModel(name);
-			if (cloneTeam.canClone(repository)) {
-				// origin team can clone origin, grant clone access to fork
-				cloneTeam.setRepositoryPermission(cloneName, AccessPermission.CLONE);
-			}
-			cloneTeams.add(cloneTeam);
-		}
-		userManager.updateTeamModels(cloneTeams);
-
-		// add this clone to the cached model
-		repositoryManager.addToCachedRepositoryList(cloneModel);
-		return cloneModel;
+	public boolean isServingRepositories() {
+		return servicesManager.isServingRepositories();
 	}
 
-	/**
-	 * Updates the TeamModel object for the specified name.
-	 *
-	 * @param teamname
-	 * @param team
-	 * @param isCreate
-	 */
-	@Override
-	public void updateTeamModel(String teamname, TeamModel team, boolean isCreate)
-			throws GitBlitException {
-		if (!teamname.equalsIgnoreCase(team.name)) {
-			if (userManager.getTeamModel(team.name) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", teamname,
-						team.name));
-			}
-		}
-		if (!userManager.updateTeamModel(teamname, team)) {
-			throw new GitBlitException(isCreate ? "Failed to add team!" : "Failed to update team!");
-		}
+	protected Object [] getModules() {
+		return new Object [] { new GitBlitModule()};
 	}
 
-	/**
-	 * 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
-	 */
-	@Override
-	public void updateUserModel(String username, UserModel user, boolean isCreate)
-			throws GitBlitException {
-		if (!username.equalsIgnoreCase(user.username)) {
-			if (userManager.getUserModel(user.username) != null) {
-				throw new GitBlitException(MessageFormat.format(
-						"Failed to rename ''{0}'' because ''{1}'' already exists.", username,
-						user.username));
+	protected boolean acceptPush(Transport byTransport) {
+		if (byTransport == null) {
+			logger.info("Unknown transport, push rejected!");
+			return false;
+		}
+
+		Set<Transport> transports = new HashSet<Transport>();
+		for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) {
+			Transport transport = Transport.fromString(value);
+			if (transport == null) {
+				logger.info(String.format("Ignoring unknown registered transport %s", value));
+				continue;
 			}
 
-			// rename repositories and owner fields for all repositories
-			for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) {
-				if (model.isUsersPersonalRepository(username)) {
-					// personal repository
-					model.addOwner(user.username);
-					String oldRepositoryName = model.name;
-					model.name = user.getPersonalPath() + model.name.substring(model.projectPath.length());
-					model.projectPath = user.getPersonalPath();
-					repositoryManager.updateRepositoryModel(oldRepositoryName, model, false);
-				} else if (model.isOwner(username)) {
-					// common/shared repo
-					model.addOwner(user.username);
-					repositoryManager.updateRepositoryModel(model.name, model, false);
-				}
-			}
+			transports.add(transport);
 		}
-		if (!userManager.updateUserModel(username, user)) {
-			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
+
+		if (transports.isEmpty()) {
+			// no transports are explicitly specified, all are acceptable
+			return true;
 		}
+
+		// verify that the transport is permitted
+		return transports.contains(byTransport);
 	}
 
 	/**
@@ -300,11 +163,33 @@
 		String username = StringUtils.encodeUsername(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)) {
+				Transport transport = Transport.fromString(request.getScheme());
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
 				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
+			}
+		}
+
+		// ssh daemon url
+		String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(sshDaemonUrl)) {
+			AccessPermission permission = user.getRepositoryPermission(repository).permission;
+			if (permission.exceeds(AccessPermission.NONE)) {
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
+
+				list.add(new RepositoryUrl(sshDaemonUrl, permission));
 			}
 		}
 
@@ -313,6 +198,11 @@
 		if (!StringUtils.isEmpty(gitDaemonUrl)) {
 			AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository);
 			if (permission.exceeds(AccessPermission.NONE)) {
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
 				list.add(new RepositoryUrl(gitDaemonUrl, permission));
 			}
 		}
@@ -331,751 +221,231 @@
 				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
 			}
 		}
+
+		// sort transports by highest permission and then by transport security
+		Collections.sort(list, new Comparator<RepositoryUrl>() {
+
+			@Override
+			public int compare(RepositoryUrl o1, RepositoryUrl o2) {
+				if (!o1.isExternal() && o2.isExternal()) {
+					// prefer Gitblit over external
+					return -1;
+				} else if (o1.isExternal() && !o2.isExternal()) {
+					// prefer Gitblit over external
+					return 1;
+				} else if (o1.isExternal() && o2.isExternal()) {
+					// sort by Transport ordinal
+					return o1.transport.compareTo(o2.transport);
+				} else if (o1.permission.exceeds(o2.permission)) {
+					// prefer highest permission
+					return -1;
+				} else if (o2.permission.exceeds(o1.permission)) {
+					// prefer highest permission
+					return 1;
+				}
+
+				// prefer more secure transports
+				return o1.transport.compareTo(o2.transport);
+			}
+		});
+
 		return list;
 	}
 
-	protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(HttpUtils.getGitblitURL(request));
-		sb.append(Constants.R_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();
-	}
-
-
 	/**
-	 * Returns the list of custom client applications to be used for the
-	 * repository url panel;
-	 *
-	 * @return a collection of client applications
+	 * Detect renames and reindex as appropriate.
 	 */
 	@Override
-	public Collection<GitClientApplication> getClientApplications() {
-		// prefer user definitions, if they exist
-		File userDefs = new File(runtimeManager.getBaseFolder(), "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);
-				}
-			}
+	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
+			boolean isCreate) throws GitBlitException {
+		RepositoryModel oldModel = null;
+		boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
+		if (isRename) {
+			oldModel = repositoryManager.getRepositoryModel(repositoryName);
 		}
 
-		// 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);
-			}
-		}
+		super.updateRepositoryModel(repositoryName, repository, isCreate);
 
-		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);
+		if (isRename && ticketService != null) {
+			ticketService.rename(oldModel, repository);
 		}
-		return null;
 	}
 
 	/**
-	 * 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>
+	 * Delete the user and all associated public ssh keys.
 	 */
-	private void loadSettingModels(ServerSettings settingsModel) {
-		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");
-		}
-	}
-
-	/*
-	 * ISTOREDSETTINGS
-	 *
-	 * these methods are necessary for (nearly) seamless Groovy hook operation
-	 * after the massive refactor.
-	 */
-
-	public boolean getBoolean(String key, boolean defaultValue) {
-		return runtimeManager.getSettings().getBoolean(key, defaultValue);
-	}
-
-	public String getString(String key, String defaultValue) {
-		return runtimeManager.getSettings().getString(key, defaultValue);
-	}
-
-	public int getInteger(String key, int defaultValue) {
-		return runtimeManager.getSettings().getInteger(key, defaultValue);
-	}
-
-	public List<String> getStrings(String key) {
-		return runtimeManager.getSettings().getStrings(key);
-	}
-
-	/*
-	 * RUNTIME MANAGER
-	 */
-
-	@Override
-	public File getBaseFolder() {
-		return runtimeManager.getBaseFolder();
-	}
-
-	@Override
-	public void setBaseFolder(File folder) {
-		runtimeManager.setBaseFolder(folder);
-	}
-
-	@Override
-	public Date getBootDate() {
-		return runtimeManager.getBootDate();
-	}
-
-	@Override
-	public ServerSettings getSettingsModel() {
-		return runtimeManager.getSettingsModel();
-	}
-
-	@Override
-	public boolean isServingRepositories() {
-		return runtimeManager.isServingRepositories();
-	}
-
-	@Override
-	public TimeZone getTimezone() {
-		return runtimeManager.getTimezone();
-	}
-
-	@Override
-	public boolean isDebugMode() {
-		return runtimeManager.isDebugMode();
-	}
-
-	@Override
-	public File getFileOrFolder(String key, String defaultFileOrFolder) {
-		return runtimeManager.getFileOrFolder(key, defaultFileOrFolder);
-	}
-
-	@Override
-	public File getFileOrFolder(String fileOrFolder) {
-		return runtimeManager.getFileOrFolder(fileOrFolder);
-	}
-
-	@Override
-	public IStoredSettings getSettings() {
-		return runtimeManager.getSettings();
-	}
-
-	@Override
-	public boolean updateSettings(Map<String, String> updatedSettings) {
-		return runtimeManager.updateSettings(updatedSettings);
-	}
-
-	@Override
-	public ServerStatus getStatus() {
-		return runtimeManager.getStatus();
-	}
-
-	/*
-	 * NOTIFICATION MANAGER
-	 */
-
-	@Override
-	public void sendMailToAdministrators(String subject, String message) {
-		notificationManager.sendMailToAdministrators(subject, message);
-	}
-
-	@Override
-	public void sendMail(String subject, String message, Collection<String> toAddresses) {
-		notificationManager.sendMail(subject, message, toAddresses);
-	}
-
-	@Override
-	public void sendMail(String subject, String message, String... toAddresses) {
-		notificationManager.sendMail(subject, message, toAddresses);
-	}
-
-	@Override
-	public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
-		notificationManager.sendHtmlMail(subject, message, toAddresses);
-	}
-
-	@Override
-	public void sendHtmlMail(String subject, String message, String... toAddresses) {
-		notificationManager.sendHtmlMail(subject, message, toAddresses);
-	}
-
-	/*
-	 * SESSION MANAGER
-	 */
-
-	@Override
-	public UserModel authenticate(String username, char[] password) {
-		return authenticationManager.authenticate(username, password);
-	}
-
-	@Override
-	public UserModel authenticate(HttpServletRequest httpRequest) {
-		return authenticationManager.authenticate(httpRequest, false);
-	}
-	@Override
-	public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
-		return authenticationManager.authenticate(httpRequest, requiresCertificate);
-	}
-
-	@Override
-	public void setCookie(HttpServletResponse response, UserModel user) {
-		authenticationManager.setCookie(response, user);
-	}
-
-	@Override
-	public void logout(HttpServletResponse response, UserModel user) {
-		authenticationManager.logout(response, user);
-	}
-
-	@Override
-	public boolean supportsCredentialChanges(UserModel user) {
-		return authenticationManager.supportsCredentialChanges(user);
-	}
-
-	@Override
-	public boolean supportsDisplayNameChanges(UserModel user) {
-		return authenticationManager.supportsDisplayNameChanges(user);
-	}
-
-	@Override
-	public boolean supportsEmailAddressChanges(UserModel user) {
-		return authenticationManager.supportsEmailAddressChanges(user);
-	}
-
-	@Override
-	public boolean supportsTeamMembershipChanges(UserModel user) {
-		return authenticationManager.supportsTeamMembershipChanges(user);
-	}
-
-	@Override
-	public boolean supportsTeamMembershipChanges(TeamModel team) {
-		return authenticationManager.supportsTeamMembershipChanges(team);
-	}
-
-	/*
-	 * USER MANAGER
-	 */
-
-	@Override
-	public void setup(IRuntimeManager runtimeManager) {
-	}
-
-	@Override
-	public List<String> getAllUsernames() {
-		return userManager.getAllUsernames();
-	}
-
-	@Override
-	public List<UserModel> getAllUsers() {
-		return userManager.getAllUsers();
-	}
-
 	@Override
 	public boolean deleteUser(String username) {
-		return userManager.deleteUser(username);
-	}
-
-	@Override
-	public UserModel getUserModel(String username) {
-		return userManager.getUserModel(username);
-	}
-
-	@Override
-	public List<TeamModel> getAllTeams() {
-		return userManager.getAllTeams();
-	}
-
-	@Override
-	public TeamModel getTeamModel(String teamname) {
-		return userManager.getTeamModel(teamname);
-	}
-
-	@Override
-	public String getCookie(UserModel model) {
-		return userManager.getCookie(model);
-	}
-
-	@Override
-	public UserModel getUserModel(char[] cookie) {
-		return userManager.getUserModel(cookie);
-	}
-
-	@Override
-	public boolean updateUserModel(UserModel model) {
-		return userManager.updateUserModel(model);
-	}
-
-	@Override
-	public boolean updateUserModels(Collection<UserModel> models) {
-		return userManager.updateUserModels(models);
-	}
-
-	@Override
-	public boolean updateUserModel(String username, UserModel model) {
-		return userManager.updateUserModel(username, model);
+		UserModel user = userManager.getUserModel(username);
+		return deleteUserModel(user);
 	}
 
 	@Override
 	public boolean deleteUserModel(UserModel model) {
-		return userManager.deleteUserModel(model);
+		boolean success = userManager.deleteUserModel(model);
+		if (success) {
+			getPublicKeyManager().removeAllKeys(model.username);
+		}
+		return success;
 	}
 
-	@Override
-	public List<String> getAllTeamNames() {
-		return userManager.getAllTeamNames();
-	}
-
-	@Override
-	public List<String> getTeamNamesForRepositoryRole(String role) {
-		return userManager.getTeamNamesForRepositoryRole(role);
-	}
-
-	@Override
-	public boolean updateTeamModel(TeamModel model) {
-		return userManager.updateTeamModel(model);
-	}
-
-	@Override
-	public boolean updateTeamModels(Collection<TeamModel> models) {
-		return userManager.updateTeamModels(models);
-	}
-
-	@Override
-	public boolean updateTeamModel(String teamname, TeamModel model) {
-		return userManager.updateTeamModel(teamname, model);
-	}
-
-	@Override
-	public boolean deleteTeamModel(TeamModel model) {
-		return userManager.deleteTeamModel(model);
-	}
-
-	@Override
-	public List<String> getUsernamesForRepositoryRole(String role) {
-		return userManager.getUsernamesForRepositoryRole(role);
-	}
-
-	@Override
-	public boolean renameRepositoryRole(String oldRole, String newRole) {
-		return userManager.renameRepositoryRole(oldRole, newRole);
-	}
-
-	@Override
-	public boolean deleteRepositoryRole(String role) {
-		return userManager.deleteRepositoryRole(role);
-	}
-
-	@Override
-	public boolean deleteTeam(String teamname) {
-		return userManager.deleteTeam(teamname);
-	}
-
-	/*
-	 * REPOSITORY MANAGER
+	/**
+	 * Delete the repository and all associated tickets.
 	 */
-
 	@Override
-	public Date getLastActivityDate() {
-		return repositoryManager.getLastActivityDate();
-	}
-
-	@Override
-	public File getRepositoriesFolder() {
-		return repositoryManager.getRepositoriesFolder();
-	}
-
-	@Override
-	public File getHooksFolder() {
-		return repositoryManager.getHooksFolder();
-	}
-
-	@Override
-	public File getGrapesFolder() {
-		return repositoryManager.getGrapesFolder();
-	}
-
-	@Override
-	public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
-		return repositoryManager.getUserAccessPermissions(user);
-	}
-
-	@Override
-	public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
-		return repositoryManager.getUserAccessPermissions(repository);
-	}
-
-	@Override
-	public boolean setUserAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
-		return repositoryManager.setUserAccessPermissions(repository, permissions);
-	}
-
-	@Override
-	public List<String> getRepositoryUsers(RepositoryModel repository) {
-		return repositoryManager.getRepositoryUsers(repository);
-	}
-
-	@Override
-	public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
-		return repositoryManager.getTeamAccessPermissions(repository);
-	}
-
-	@Override
-	public boolean setTeamAccessPermissions(RepositoryModel repository, Collection<RegistrantAccessPermission> permissions) {
-		return repositoryManager.setTeamAccessPermissions(repository, permissions);
-	}
-
-	@Override
-	public List<String> getRepositoryTeams(RepositoryModel repository) {
-		return repositoryManager.getRepositoryTeams(repository);
-	}
-	@Override
-	public void addToCachedRepositoryList(RepositoryModel model) {
-		repositoryManager.addToCachedRepositoryList(model);
-	}
-
-	@Override
-	public void resetRepositoryListCache() {
-		repositoryManager.resetRepositoryListCache();
-	}
-
-	@Override
-	public List<String> getRepositoryList() {
-		return repositoryManager.getRepositoryList();
-	}
-
-	@Override
-	public Repository getRepository(String repositoryName) {
-		return repositoryManager.getRepository(repositoryName);
-	}
-
-	@Override
-	public Repository getRepository(String repositoryName, boolean logError) {
-		return repositoryManager.getRepository(repositoryName, logError);
-	}
-
-	@Override
-	public List<RepositoryModel> getRepositoryModels(UserModel user) {
-		return repositoryManager.getRepositoryModels(user);
-	}
-
-	@Override
-	public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
-		return repositoryManager.getRepositoryModel(repositoryName);
-	}
-
-	@Override
-	public RepositoryModel getRepositoryModel(String repositoryName) {
-		return repositoryManager.getRepositoryModel(repositoryName);
-	}
-
-	@Override
-	public long getStarCount(RepositoryModel repository) {
-		return repositoryManager.getStarCount(repository);
-	}
-
-	@Override
-	public boolean hasRepository(String repositoryName) {
-		return repositoryManager.hasRepository(repositoryName);
-	}
-
-	@Override
-	public boolean hasRepository(String repositoryName, boolean caseSensitiveCheck) {
-		return repositoryManager.hasRepository(repositoryName, caseSensitiveCheck);
-	}
-
-	@Override
-	public boolean hasFork(String username, String origin) {
-		return repositoryManager.hasFork(username, origin);
-	}
-
-	@Override
-	public String getFork(String username, String origin) {
-		return repositoryManager.getFork(username, origin);
-	}
-
-	@Override
-	public ForkModel getForkNetwork(String repository) {
-		return repositoryManager.getForkNetwork(repository);
-	}
-
-	@Override
-	public long updateLastChangeFields(Repository r, RepositoryModel model) {
-		return repositoryManager.updateLastChangeFields(r, model);
-	}
-
-	@Override
-	public List<Metric> getRepositoryDefaultMetrics(RepositoryModel model, Repository repository) {
-		return repositoryManager.getRepositoryDefaultMetrics(model, repository);
-	}
-
-	@Override
-	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
-			boolean isCreate) throws GitBlitException {
-		repositoryManager.updateRepositoryModel(repositoryName, repository, isCreate);
-	}
-
-	@Override
-	public void updateConfiguration(Repository r, RepositoryModel repository) {
-		repositoryManager.updateConfiguration(r, repository);
+	public boolean deleteRepository(String repositoryName) {
+		RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
+		return deleteRepositoryModel(repository);
 	}
 
 	@Override
 	public boolean deleteRepositoryModel(RepositoryModel model) {
-		return repositoryManager.deleteRepositoryModel(model);
+		boolean success = repositoryManager.deleteRepositoryModel(model);
+		if (success && ticketService != null) {
+			ticketService.deleteAll(model);
+		}
+		return success;
 	}
 
-	@Override
-	public boolean deleteRepository(String repositoryName) {
-		return repositoryManager.deleteRepository(repositoryName);
-	}
-
-	@Override
-	public List<String> getAllScripts() {
-		return repositoryManager.getAllScripts();
-	}
-
-	@Override
-	public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
-		return repositoryManager.getPreReceiveScriptsInherited(repository);
-	}
-
-	@Override
-	public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
-		return repositoryManager.getPreReceiveScriptsUnused(repository);
-	}
-
-	@Override
-	public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
-		return repositoryManager.getPostReceiveScriptsInherited(repository);
-	}
-
-	@Override
-	public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
-		return repositoryManager.getPostReceiveScriptsUnused(repository);
-	}
-
-	@Override
-	public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) {
-		return repositoryManager.search(query, page, pageSize, repositories);
-	}
-
-	@Override
-	public boolean isCollectingGarbage() {
-		return repositoryManager.isCollectingGarbage();
-	}
-
-	@Override
-	public boolean isCollectingGarbage(String repositoryName) {
-		return repositoryManager.isCollectingGarbage(repositoryName);
-	}
-
-	/*
-	 * PROJECT MANAGER
+	/**
+	 * Returns the configured ticket service.
+	 *
+	 * @return a ticket service
 	 */
-
 	@Override
-	public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) {
-		return projectManager.getProjectModels(user, includeUsers);
+	public ITicketService getTicketService() {
+		return ticketService;
 	}
 
-	@Override
-	public ProjectModel getProjectModel(String name, UserModel user) {
-		return projectManager.getProjectModel(name, user);
+	protected void configureTicketService() {
+		String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = NullTicketService.class.getName();
+		}
+		try {
+			Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz);
+			ticketService = injector.get(serviceClass).start();
+			if (ticketService instanceof NullTicketService) {
+				logger.warn("No ticket service configured.");
+			} else if (ticketService.isReady()) {
+				logger.info("{} is ready.", ticketService);
+			} else {
+				logger.warn("{} is disabled.", ticketService);
+			}
+		} catch (Exception e) {
+			logger.error("failed to create ticket service " + clazz, e);
+			ticketService = injector.get(NullTicketService.class).start();
+		}
 	}
 
-	@Override
-	public ProjectModel getProjectModel(String name) {
-		return projectManager.getProjectModel(name);
-	}
-
-	@Override
-	public List<ProjectModel> getProjectModels(List<RepositoryModel> repositoryModels, boolean includeUsers) {
-		return projectManager.getProjectModels(repositoryModels, includeUsers);
-	}
-
-	/*
-	 * FEDERATION MANAGER
+	/**
+	 * A nested Dagger graph is used for constructor dependency injection of
+	 * complex classes.
+	 *
+	 * @author James Moger
+	 *
 	 */
+	@Module(
+			library = true,
+			injects = {
+					IStoredSettings.class,
 
-	@Override
-	public File getProposalsFolder() {
-		return federationManager.getProposalsFolder();
-	}
+					// core managers
+					IRuntimeManager.class,
+					IPluginManager.class,
+					INotificationManager.class,
+					IUserManager.class,
+					IAuthenticationManager.class,
+					IRepositoryManager.class,
+					IProjectManager.class,
+					IFederationManager.class,
 
-	@Override
-	public UserModel getFederationUser() {
-		return federationManager.getFederationUser();
-	}
+					// the monolithic manager
+					IGitblit.class,
 
-	@Override
-	public boolean canFederate() {
-		return federationManager.canFederate();
-	}
+					// ticket services
+					NullTicketService.class,
+					FileTicketService.class,
+					BranchTicketService.class,
+					RedisTicketService.class
+				}
+			)
+	class GitBlitModule {
 
-	@Override
-	public List<FederationModel> getFederationRegistrations() {
-		return federationManager.getFederationRegistrations();
-	}
+		@Provides @Singleton IStoredSettings provideSettings() {
+			return settings;
+		}
 
-	@Override
-	public FederationModel getFederationRegistration(String url, String name) {
-		return federationManager.getFederationRegistration(url, name);
-	}
+		@Provides @Singleton IRuntimeManager provideRuntimeManager() {
+			return runtimeManager;
+		}
 
-	@Override
-	public List<FederationSet> getFederationSets(String gitblitUrl) {
-		return federationManager.getFederationSets(gitblitUrl);
-	}
+		@Provides @Singleton IPluginManager providePluginManager() {
+			return pluginManager;
+		}
 
-	@Override
-	public List<String> getFederationTokens() {
-		return federationManager.getFederationTokens();
-	}
+		@Provides @Singleton INotificationManager provideNotificationManager() {
+			return notificationManager;
+		}
 
-	@Override
-	public String getFederationToken(FederationToken type) {
-		return federationManager.getFederationToken(type);
-	}
+		@Provides @Singleton IUserManager provideUserManager() {
+			return userManager;
+		}
 
-	@Override
-	public String getFederationToken(String value) {
-		return federationManager.getFederationToken(value);
-	}
+		@Provides @Singleton IAuthenticationManager provideAuthenticationManager() {
+			return authenticationManager;
+		}
 
-	@Override
-	public boolean validateFederationRequest(FederationRequest req, String token) {
-		return federationManager.validateFederationRequest(req, token);
-	}
+		@Provides @Singleton IRepositoryManager provideRepositoryManager() {
+			return repositoryManager;
+		}
 
-	@Override
-	public boolean acknowledgeFederationStatus(String identification, FederationModel registration) {
-		return federationManager.acknowledgeFederationStatus(identification, registration);
-	}
+		@Provides @Singleton IProjectManager provideProjectManager() {
+			return projectManager;
+		}
 
-	@Override
-	public List<FederationModel> getFederationResultRegistrations() {
-		return federationManager.getFederationResultRegistrations();
-	}
+		@Provides @Singleton IFederationManager provideFederationManager() {
+			return federationManager;
+		}
 
-	@Override
-	public boolean submitFederationProposal(FederationProposal proposal, String gitblitUrl) {
-		return federationManager.submitFederationProposal(proposal, gitblitUrl);
-	}
+		@Provides @Singleton IGitblit provideGitblit() {
+			return GitBlit.this;
+		}
 
-	@Override
-	public List<FederationProposal> getPendingFederationProposals() {
-		return federationManager.getPendingFederationProposals();
-	}
+		@Provides @Singleton NullTicketService provideNullTicketService() {
+			return new NullTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 
-	@Override
-	public Map<String, RepositoryModel> getRepositories(String gitblitUrl, String token) {
-		return federationManager.getRepositories(gitblitUrl, token);
-	}
+		@Provides @Singleton FileTicketService provideFileTicketService() {
+			return new FileTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 
-	@Override
-	public FederationProposal createFederationProposal(String gitblitUrl, String token) {
-		return federationManager.createFederationProposal(gitblitUrl, token);
-	}
+		@Provides @Singleton BranchTicketService provideBranchTicketService() {
+			return new BranchTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 
-	@Override
-	public FederationProposal getPendingFederationProposal(String token) {
-		return federationManager.getPendingFederationProposal(token);
-	}
-
-	@Override
-	public boolean deletePendingFederationProposal(FederationProposal proposal) {
-		return federationManager.deletePendingFederationProposal(proposal);
+		@Provides @Singleton RedisTicketService provideRedisTicketService() {
+			return new RedisTicketService(
+					runtimeManager,
+					pluginManager,
+					notificationManager,
+					userManager,
+					repositoryManager);
+		}
 	}
 }

--
Gitblit v1.9.1