From 8a2e9c363346ef5bf48c8eba09cb8afa46fabeeb Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 24 May 2011 17:17:51 -0400
Subject: [PATCH] Delete/Rename repos & user. Edit link. Dropped crypt. Other git urls.

---
 src/com/gitblit/ILoginService.java                   |    7 
 src/com/gitblit/utils/StringUtils.java               |    6 
 src/com/gitblit/wicket/pages/SummaryPage.html        |    2 
 src/com/gitblit/wicket/pages/RepositoriesPage.java   |    1 
 src/com/gitblit/wicket/panels/UsersPanel.java        |   33 +++
 src/com/gitblit/wicket/pages/EditRepositoryPage.java |   12 
 src/com/gitblit/wicket/RepositoryPage.java           |   22 ++
 src/com/gitblit/wicket/LoginPage.java                |   27 --
 src/com/gitblit/wicket/models/RepositoryModel.java   |    7 
 src/com/gitblit/wicket/pages/EditUserPage.java       |   16 -
 distrib/gitblit.properties                           |    8 
 src/com/gitblit/wicket/models/UserModel.java         |   10 -
 src/com/gitblit/wicket/panels/RepositoriesPanel.java |  125 +++++++++++--
 src/com/gitblit/wicket/panels/BasePanel.java         |   20 ++
 /dev/null                                            |    0 
 src/com/gitblit/GitBlit.java                         |  103 +++++++----
 src/com/gitblit/JettyLoginService.java               |   29 +-
 src/com/gitblit/wicket/panels/RepositoriesPanel.html |    2 
 src/com/gitblit/wicket/pages/SummaryPage.java        |   64 +++++--
 19 files changed, 344 insertions(+), 150 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index d0e6375..b263f32 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -17,8 +17,10 @@
 # e.g. /libraries/mylibrary.git
 git.nestedRepositories = true
 
-# The root clone url
-git.cloneUrl = https://localhost/git/
+# Show other URLs on the summary page for accessing your git repositories
+# Use spaces to separate urls. {0} is the token for the repository name.
+# git.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0}
+git.otherUrls = 
 
 #
 # Authentication Settings
@@ -34,7 +36,7 @@
 realm.realmFile = users.properties
 
 # How to store passwords.
-# Valid values are plain, md5 or crypt (unix style).  Default is md5. 
+# Valid values are plain or md5.  Default is md5. 
 realm.passwordStorage = md5
 
 # Minimum valid length for a plain text password.
diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 51c3b45..acb90d8 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -9,14 +9,13 @@
 
 import javax.servlet.ServletContextEvent;
 import javax.servlet.ServletContextListener;
-import javax.servlet.http.Cookie;
 
-import org.apache.wicket.protocol.http.WebResponse;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.transport.resolver.FileResolver;
 import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException;
+import org.eclipse.jgit.util.FileUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -61,8 +60,12 @@
 		return storedSettings.getBoolean(Keys.web.debugMode, false);
 	}
 
-	public String getCloneUrl(String repositoryName) {
-		return storedSettings.getString(Keys.git.cloneUrl, "https://localhost/git/") + repositoryName;
+	public List<String> getOtherCloneUrls(String repositoryName) {
+		List<String> cloneUrls = new ArrayList<String>();
+		for (String url : storedSettings.getStrings(Keys.git.otherUrls)) {
+			cloneUrls.add(MessageFormat.format(url, repositoryName));
+		}
+		return cloneUrls;
 	}
 
 	public void setLoginService(ILoginService loginService) {
@@ -76,49 +79,31 @@
 		return loginService.authenticate(username, password);
 	}
 
-	public UserModel authenticate(Cookie[] cookies) {
-		if (loginService == null) {
-			return null;
-		}
-		if (cookies != null && cookies.length > 0) {
-			for (Cookie cookie : cookies) {
-				if (cookie.getName().equals(Constants.NAME)) {
-					String value = cookie.getValue();
-					return loginService.authenticate(value.toCharArray());
-				}
-			}
-		}
-		return null;
-	}
-
-	public void setCookie(WebResponse response, UserModel user) {
-		Cookie userCookie = new Cookie(Constants.NAME, user.getCookie());
-		userCookie.setMaxAge(Integer.MAX_VALUE);
-		userCookie.setPath("/");
-		response.addCookie(userCookie);
-	}
-	
 	public List<String> getAllUsernames() {
-		List<String> names = loginService.getAllUsernames();
+		List<String> names = new ArrayList<String>(loginService.getAllUsernames());
 		Collections.sort(names);
 		return names;
+	}
+
+	public boolean deleteUser(String username) {
+		return loginService.deleteUser(username);
 	}
 
 	public UserModel getUserModel(String username) {
 		UserModel user = loginService.getUserModel(username);
 		return user;
 	}
-	
+
 	public List<String> getRepositoryUsers(RepositoryModel repository) {
 		return loginService.getUsernamesForRole(repository.name);
 	}
-	
+
 	public boolean setRepositoryUsers(RepositoryModel repository, List<String> repositoryUsers) {
 		return loginService.setUsernamesForRole(repository.name, repositoryUsers);
 	}
 
-	public void editUserModel(UserModel user, boolean isCreate) throws GitBlitException {
-		if (!loginService.updateUserModel(user)) {
+	public void editUserModel(String username, UserModel user, boolean isCreate) throws GitBlitException {
+		if (!loginService.updateUserModel(username, user)) {
 			throw new GitBlitException(isCreate ? "Failed to add user!" : "Failed to update user!");
 		}
 	}
@@ -152,7 +137,7 @@
 		}
 		return repositories;
 	}
-	
+
 	public RepositoryModel getRepositoryModel(UserModel user, String repositoryName) {
 		RepositoryModel model = getRepositoryModel(repositoryName);
 		if (model.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
@@ -184,7 +169,7 @@
 		r.close();
 		return model;
 	}
-	
+
 	private String getConfig(StoredConfig config, String field, String defaultValue) {
 		String value = config.getString("gitblit", null, field);
 		if (StringUtils.isEmpty(value)) {
@@ -192,21 +177,37 @@
 		}
 		return value;
 	}
-	
+
 	private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
 		return config.getBoolean("gitblit", field, defaultValue);
 	}
 
-	public void editRepositoryModel(RepositoryModel repository, boolean isCreate) throws GitBlitException {
+	public void editRepositoryModel(String repositoryName, RepositoryModel repository, boolean isCreate) throws GitBlitException {
 		Repository r = null;
 		if (isCreate) {
 			if (new File(repositoriesFolder, repository.name).exists()) {
-				throw new GitBlitException(MessageFormat.format("Can not create repository {0} because it already exists.", 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, true);
 		} else {
+			// rename repository
+			if (!repositoryName.equalsIgnoreCase(repository.name)) {
+				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));
+				}
+				if (!folder.renameTo(destFolder)) {
+					throw new GitBlitException(MessageFormat.format("Failed to rename repository ''{0}'' to ''{1}''.", repositoryName, repository.name));
+				}
+				// rename the roles
+				if (!loginService.renameRole(repositoryName, repository.name)) {
+					throw new GitBlitException(MessageFormat.format("Failed to rename repository permissions ''{0}'' to ''{1}''.", repositoryName, repository.name));
+				}
+			}
+
 			// load repository
 			logger.info("edit repository " + repository.name);
 			try {
@@ -235,6 +236,36 @@
 		r.close();
 	}
 
+	public boolean deleteRepositoryModel(RepositoryModel model) {
+		return deleteRepository(model.name);
+	}
+
+	public boolean deleteRepository(String repositoryName) {
+		try {
+			File folder = new File(repositoriesFolder, repositoryName);
+			if (folder.exists() && folder.isDirectory()) {
+				FileUtils.delete(folder, FileUtils.RECURSIVE);
+				if (loginService.deleteRole(repositoryName)) {
+					return true;
+				}
+			}
+		} catch (Throwable t) {
+			logger.error(MessageFormat.format("Failed to delete repository {0}", repositoryName), t);
+		}
+		return false;
+	}
+
+	public boolean renameRepository(RepositoryModel model, String newName) {
+		File folder = new File(repositoriesFolder, model.name);
+		if (folder.exists() && folder.isDirectory()) {
+			File newFolder = new File(repositoriesFolder, newName);
+			if (folder.renameTo(newFolder)) {
+				return loginService.renameRole(model.name, newName);
+			}
+		}
+		return false;
+	}
+
 	public void configureContext(IStoredSettings settings) {
 		logger.info("Using configuration from " + settings.toString());
 		this.storedSettings = settings;
diff --git a/src/com/gitblit/ILoginService.java b/src/com/gitblit/ILoginService.java
index 242ff80..a548c9a 100644
--- a/src/com/gitblit/ILoginService.java
+++ b/src/com/gitblit/ILoginService.java
@@ -8,13 +8,15 @@
 
 	UserModel authenticate(String username, char[] password);
 
-	UserModel authenticate(char[] cookie);
-	
 	UserModel getUserModel(String username);
 	
 	boolean updateUserModel(UserModel model);
 	
+	boolean updateUserModel(String username, UserModel model);
+	
 	boolean deleteUserModel(UserModel model);
+	
+	boolean deleteUser(String username);
 	
 	List<String> getAllUsernames();
 	
@@ -25,5 +27,4 @@
 	boolean renameRole(String oldRole, String newRole);
 	
 	boolean deleteRole(String role);
-	
 }
diff --git a/src/com/gitblit/JettyLoginService.java b/src/com/gitblit/JettyLoginService.java
index c191f0f..231f174 100644
--- a/src/com/gitblit/JettyLoginService.java
+++ b/src/com/gitblit/JettyLoginService.java
@@ -23,7 +23,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.models.UserModel;
 
 public class JettyLoginService extends MappedLoginService implements ILoginService {
@@ -45,7 +44,6 @@
 			return null;
 		}
 		UserModel user = new UserModel(username);
-		user.setCookie(StringUtils.getSHA1((Constants.NAME + username + new String(password))));
 		user.canAdmin(identity.isUserInRole(Constants.ADMIN_ROLE, null));
 
 		// Add repositories
@@ -59,12 +57,6 @@
 			}
 		}
 		return user;
-	}
-
-	@Override
-	public UserModel authenticate(char[] cookie) {
-		// TODO cookie login
-		return null;
 	}
 
 	@Override
@@ -107,6 +99,11 @@
 
 	@Override
 	public boolean updateUserModel(UserModel model) {
+		return updateUserModel(model.getUsername(), model);
+	}
+	
+	@Override
+	public boolean updateUserModel(String username, UserModel model) {
 		try {
 			Properties allUsers = readRealmFile();
 			ArrayList<String> roles = new ArrayList<String>(model.getRepositories());
@@ -125,11 +122,13 @@
 			}
 			// trim trailing comma
 			sb.setLength(sb.length() - 1);
+			allUsers.remove(username);
 			allUsers.put(model.getUsername(), sb.toString());
 
 			writeRealmFile(allUsers);
 
 			// Update login service
+			removeUser(username);
 			putUser(model.getUsername(), Credential.getCredential(model.getPassword()), roles.toArray(new String[0]));
 			return true;
 		} catch (Throwable t) {
@@ -140,21 +139,26 @@
 
 	@Override
 	public boolean deleteUserModel(UserModel model) {
+		return deleteUser(model.getUsername());
+	}
+
+	@Override
+	public boolean deleteUser(String username) {
 		try {
 			// Read realm file
 			Properties allUsers = readRealmFile();
-			allUsers.remove(model.getUsername());
+			allUsers.remove(username);
 			writeRealmFile(allUsers);
 
 			// Drop user from map
-			_users.remove(model.getUsername());
+			removeUser(username);
 			return true;
 		} catch (Throwable t) {
-			logger.error(MessageFormat.format("Failed to delete user model {0}!", model.getUsername()), t);
+			logger.error(MessageFormat.format("Failed to delete user {0}!", username), t);
 		}
 		return false;
 	}
-
+	
 	@Override
 	public List<String> getAllUsernames() {
 		List<String> list = new ArrayList<String>();
@@ -366,6 +370,7 @@
 
 			// persist changes
 			writeRealmFile(allUsers);
+			return true;
 		} catch (Throwable t) {
 			logger.error(MessageFormat.format("Failed to delete role {0}!", role), t);
 		}
diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java
index 8b7960b..ddb7286 100644
--- a/src/com/gitblit/utils/StringUtils.java
+++ b/src/com/gitblit/utils/StringUtils.java
@@ -40,9 +40,13 @@
 	}
 
 	public static String flattenStrings(List<String> values) {
+		return flattenStrings(values, " ");
+	}
+
+	public static String flattenStrings(List<String> values, String separator) {
 		StringBuilder sb = new StringBuilder();
 		for (String value : values) {
-			sb.append(value).append(" ");
+			sb.append(value).append(separator);
 		}
 		return sb.toString().trim();
 	}
diff --git a/src/com/gitblit/wicket/LoginPage.java b/src/com/gitblit/wicket/LoginPage.java
index db971d2..63cb18f 100644
--- a/src/com/gitblit/wicket/LoginPage.java
+++ b/src/com/gitblit/wicket/LoginPage.java
@@ -1,7 +1,5 @@
 package com.gitblit.wicket;
 
-import javax.servlet.http.Cookie;
-
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.WebPage;
 import org.apache.wicket.markup.html.basic.Label;
@@ -12,8 +10,6 @@
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
-import org.apache.wicket.protocol.http.WebRequest;
-import org.apache.wicket.protocol.http.WebResponse;
 
 import com.gitblit.Constants;
 import com.gitblit.GitBlit;
@@ -27,8 +23,6 @@
 
 	public LoginPage(PageParameters params) {
 		super(params);
-
-		tryAutomaticLogin();
 
 		add(new Label("title", GitBlit.self().settings().getString(Keys.web.siteName, Constants.NAME)));
 		add(new Label("name", Constants.NAME));
@@ -52,8 +46,6 @@
 				setRedirect(true);
 				setResponsePage(getApplication().getHomePage());
 			}
-			
-			tryAutomaticLogin();
 		}
 
 		@Override
@@ -68,28 +60,11 @@
 				loginUser(user);
 		}
 	}
-
-	private void tryAutomaticLogin() {
-		UserModel user = null;
-
-		// Grab cookie from Browser Session
-		Cookie[] cookies = ((WebRequest) getRequestCycle().getRequest()).getCookies();
-		if (cookies != null && cookies.length > 0) {
-			user = GitBlit.self().authenticate(cookies);
-		}
-
-		// Login the user
-		loginUser(user);
-	}
-
+	
 	private void loginUser(UserModel user) {
 		if (user != null) {
 			// Set the user into the session
 			GitBlitWebSession.get().setUser(user);
-
-			// Set Cookie
-			WebResponse response = (WebResponse) getRequestCycle().getResponse();
-			GitBlit.self().setCookie(response, user);
 
 			if (!continueToOriginalDestination()) {
 				// Redirect to home page
diff --git a/src/com/gitblit/wicket/RepositoryPage.java b/src/com/gitblit/wicket/RepositoryPage.java
index e3ae635..7b447bc 100644
--- a/src/com/gitblit/wicket/RepositoryPage.java
+++ b/src/com/gitblit/wicket/RepositoryPage.java
@@ -35,6 +35,7 @@
 import com.gitblit.wicket.models.RepositoryModel;
 import com.gitblit.wicket.pages.BranchesPage;
 import com.gitblit.wicket.pages.DocsPage;
+import com.gitblit.wicket.pages.EditRepositoryPage;
 import com.gitblit.wicket.pages.LogPage;
 import com.gitblit.wicket.pages.SearchPage;
 import com.gitblit.wicket.pages.SummaryPage;
@@ -65,6 +66,7 @@
 			put("tags", "gb.tags");
 			put("tree", "gb.tree");
 			put("tickets", "gb.tickets");
+			put("edit", "gb.edit");
 		}
 	};
 
@@ -90,14 +92,27 @@
 		// per-repository extra page links
 		List<String> extraPageLinks = new ArrayList<String>();
 
-		// Conditionally add tickets page
+		// Conditionally add tickets link
 		if (model.useTickets && JGitUtils.getTicketsBranch(r) != null) {
 			extraPageLinks.add("tickets");
 		}
 
-		// Conditionally add docs page
+		// Conditionally add docs link
 		if (model.useDocs) {
 			extraPageLinks.add("docs");
+		}
+
+		final boolean showAdmin;
+		if (GitBlit.self().settings().getBoolean(Keys.web.authenticateAdminPages, true)) {
+			boolean allowAdmin = GitBlit.self().settings().getBoolean(Keys.web.allowAdministration, false);
+			showAdmin = allowAdmin && GitBlitWebSession.get().canAdmin();
+		} else {
+			showAdmin = GitBlit.self().settings().getBoolean(Keys.web.allowAdministration, false);
+		}
+		
+		// Conditionally add edit link
+		if (showAdmin || GitBlitWebSession.get().isLoggedIn() && (model.owner != null && model.owner.equalsIgnoreCase(GitBlitWebSession.get().getUser().getUsername()))) {
+			extraPageLinks.add("edit");
 		}
 
 		ListDataProvider<String> extrasDp = new ListDataProvider<String>(extraPageLinks);
@@ -112,6 +127,9 @@
 				} else if (extra.equals("docs")) {
 					item.add(new Label("extraSeparator", " | "));
 					item.add(new LinkPanel("extraLink", null, getString("gb.docs"), DocsPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
+				} else if (extra.equals("edit")) {
+					item.add(new Label("extraSeparator", " | "));
+					item.add(new LinkPanel("extraLink", null, getString("gb.edit"), EditRepositoryPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
 				}
 			}
 		};
diff --git a/src/com/gitblit/wicket/models/RepositoryModel.java b/src/com/gitblit/wicket/models/RepositoryModel.java
index 2aabfb1..e7b5249 100644
--- a/src/com/gitblit/wicket/models/RepositoryModel.java
+++ b/src/com/gitblit/wicket/models/RepositoryModel.java
@@ -33,5 +33,10 @@
 		this.owner = owner;
 		this.lastChange = lastchange;
 		this.accessRestriction = AccessRestrictionType.NONE;
-	}	
+	}
+	
+	@Override
+	public String toString() {
+		return name;
+	}
 }
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/models/UserModel.java b/src/com/gitblit/wicket/models/UserModel.java
index 34c32e7..252bcfa 100644
--- a/src/com/gitblit/wicket/models/UserModel.java
+++ b/src/com/gitblit/wicket/models/UserModel.java
@@ -10,7 +10,6 @@
 
 	private String username;
 	private String password;
-	private String cookie;
 	private boolean canAdmin = false;
 	private List<String> repositories = new ArrayList<String>();
 
@@ -42,14 +41,6 @@
 		return canAdmin || repositories.contains(repositoryName);
 	}
 
-	public void setCookie(String cookie) {
-		this.cookie = cookie;
-	}
-
-	public String getCookie() {
-		return cookie;
-	}
-
 	public void setRepositories(List<String> repositories) {
 		this.repositories.clear();
 		this.repositories.addAll(repositories);
@@ -63,6 +54,7 @@
 		return repositories;
 	}
 
+	@Override
 	public String toString() {
 		return username;
 	}
diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
index 56d1d55..20a9c73 100644
--- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -36,6 +36,8 @@
 
 	private final boolean isCreate;
 
+	private boolean isAdmin = false;
+	
 	public EditRepositoryPage() {
 		// create constructor
 		super();
@@ -67,6 +69,7 @@
 			}
 		}
 
+		final String oldName = repositoryModel.name;
 		final Palette<String> usersPalette = new Palette<String>("users", new ListModel<String>(repositoryUsers), new CollectionModel<String>(GitBlit.self().getAllUsernames()), new ChoiceRenderer<String>("", ""), 10, false);
 		CompoundPropertyModel<RepositoryModel> model = new CompoundPropertyModel<RepositoryModel>(repositoryModel);
 		Form<RepositoryModel> form = new Form<RepositoryModel>("editForm", model) {
@@ -94,7 +97,7 @@
 								ok |= c == vc;
 							}
 							if (!ok) {
-								error(MessageFormat.format("Illegal character '{0}' in repository name!", c));
+								error(MessageFormat.format("Illegal character ''{0}'' in repository name!", c));
 								return;
 							}
 						}
@@ -107,7 +110,7 @@
 					}
 
 					// save the repository
-					GitBlit.self().editRepositoryModel(repositoryModel, isCreate);
+					GitBlit.self().editRepositoryModel(oldName, repositoryModel, isCreate);
 
 					// save the repository access list
 					if (repositoryModel.accessRestriction.exceeds(AccessRestrictionType.NONE)) {
@@ -117,7 +120,7 @@
 							repositoryUsers.add(users.next());
 						}
 						// ensure the owner is added to the user list
-						if (!repositoryUsers.contains(repositoryModel.owner)) {
+						if (repositoryModel.owner != null && !repositoryUsers.contains(repositoryModel.owner)) {
 							repositoryUsers.add(repositoryModel.owner);
 						}
 						GitBlit.self().setRepositoryUsers(repositoryModel, repositoryUsers);
@@ -132,7 +135,7 @@
 		};
 
 		// field names reflective match RepositoryModel fields
-		form.add(new TextField<String>("name").setEnabled(isCreate));
+		form.add(new TextField<String>("name").setEnabled(isCreate || isAdmin));
 		form.add(new TextField<String>("description"));
 		form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames()).setEnabled(GitBlitWebSession.get().canAdmin()));
 		form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays.asList(AccessRestrictionType.values()), new AccessRestrictionRenderer()));
@@ -175,6 +178,7 @@
 					// Edit Repository
 					if (user.canAdmin()) {
 						// Admins can edit everything
+						isAdmin = true;
 						return;
 					} else {
 						if (!model.owner.equalsIgnoreCase(user.getUsername())) {
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
index 7522f3e..4a6882f 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/com/gitblit/wicket/pages/EditUserPage.java
@@ -67,6 +67,7 @@
 				repos.add(repo);
 			}
 		}
+		final String oldName = userModel.getUsername();
 		final Palette<String> repositories = new Palette<String>("repositories", new ListModel<String>(userModel.getRepositories()), new CollectionModel<String>(repos), new ChoiceRenderer<String>("", ""), 10, false);
 		Form<UserModel> form = new Form<UserModel>("editForm", model) {
 
@@ -87,7 +88,7 @@
 				if (isCreate) {
 					UserModel model = GitBlit.self().getUserModel(username);
 					if (model != null) {
-						error(MessageFormat.format("Username {0} is unavailable.", username));
+						error(MessageFormat.format("Username ''{0}'' is unavailable.", username));
 						return;
 					}
 				}
@@ -108,14 +109,11 @@
 						return;
 					}
 					
-					// Optionally encrypt/obfuscate the password.
+					// Optionally store the password MD5 digest.
 					String type = GitBlit.self().settings().getString(Keys.realm.passwordStorage, "md5");
 					if (type.equalsIgnoreCase("md5")) {
-						// store MD5 checksum of password
+						// store MD5 digest of password
 						userModel.setPassword(MD5.digest(userModel.getPassword()));
-					} else if (type.equalsIgnoreCase("crypt")) {
-						// simple unix encryption
-						userModel.setPassword(Crypt.crypt(userModel.getUsername(), userModel.getPassword()));
 					}
 				}
 
@@ -126,7 +124,7 @@
 				}
 				userModel.setRepositories(repos);
 				try {
-					GitBlit.self().editUserModel(userModel, isCreate);
+					GitBlit.self().editUserModel(oldName, userModel, isCreate);
 				} catch (GitBlitException e) {
 					error(e.getMessage());
 					return;
@@ -134,7 +132,7 @@
 				setRedirect(false);
 				if (isCreate) {
 					// create another user
-					info(MessageFormat.format("New user {0} successfully created.", userModel.getUsername()));
+					info(MessageFormat.format("New user ''{0}'' successfully created.", userModel.getUsername()));
 					setResponsePage(EditUserPage.class);
 				} else {
 					// back to home
@@ -144,7 +142,7 @@
 		};
 
 		// field names reflective match UserModel fields
-		form.add(new TextField<String>("username").setEnabled(isCreate));
+		form.add(new TextField<String>("username"));
 		PasswordTextField passwordField = new PasswordTextField("password");
 		passwordField.setResetPassword(false);
 		form.add(passwordField);
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java
index 32552f7..e421c89 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -46,7 +46,6 @@
 		String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
 		if (!StringUtils.isEmpty(cachedMessage)) {
 			error(cachedMessage);
-			System.out.println("displayed message");
 		}
 
 		// Load the markdown welcome message
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.html b/src/com/gitblit/wicket/pages/SummaryPage.html
index 35331f5..1a90c99 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.html
+++ b/src/com/gitblit/wicket/pages/SummaryPage.html
@@ -20,7 +20,7 @@
 				<tr><th><wicket:message key="gb.owner">[owner]</wicket:message></th><td><span wicket:id="repositoryOwner">[repository owner]</span></td></tr>
 				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
 				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="repositoryStats">[repository stats]</span></td></tr>
-				<tr><th><wicket:message key="gb.url">[URL]</wicket:message></th><td><img style="vertical-align: top; padding-right:5px;" wicket:id="accessRestrictionIcon" /><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr>
+				<tr><th valign="top"><wicket:message key="gb.url">[URL]</wicket:message></th><td><img style="vertical-align: top; padding-right:5px;" wicket:id="accessRestrictionIcon" /><span wicket:id="repositoryCloneUrl">[repository clone url]</span></td></tr>
 			</table>
 		</div>
 	</div>
diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java
index d83f596..c596e87 100644
--- a/src/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/com/gitblit/wicket/pages/SummaryPage.java
@@ -3,10 +3,14 @@
 import java.awt.Color;
 import java.awt.Dimension;
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.protocol.http.WebRequest;
 import org.eclipse.jgit.lib.Repository;
 import org.wicketstuff.googlecharts.AbstractChartData;
 import org.wicketstuff.googlecharts.Chart;
@@ -19,10 +23,12 @@
 import org.wicketstuff.googlecharts.MarkerType;
 import org.wicketstuff.googlecharts.ShapeMarker;
 
+import com.gitblit.Constants;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
 import com.gitblit.wicket.RepositoryPage;
 import com.gitblit.wicket.WicketUtils;
@@ -67,25 +73,45 @@
 		} else {
 			add(new Label("repositoryStats", MessageFormat.format("{0} commits and {1} tags in {2}", metricsTotal.count, metricsTotal.tag, TimeUtils.duration(metricsTotal.duration))));
 		}
-		
-		AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction;
-		switch (accessRestriction) {
-		case NONE:
-			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-			break;
-		case PUSH:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", getAccessRestrictions().get(accessRestriction)));
-			break;
-		case CLONE:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", getAccessRestrictions().get(accessRestriction)));
-			break;
-		case VIEW:
-			add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", getAccessRestrictions().get(accessRestriction)));
-			break;
-		default:
+
+		List<String> repositoryUrls = new ArrayList<String>();
+
+		if (GitBlit.self().settings().getBoolean(Keys.git.enableGitServlet, true)) {
+			AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction;
+			switch (accessRestriction) {
+			case NONE:
+				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+				break;
+			case PUSH:
+				add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", getAccessRestrictions().get(accessRestriction)));
+				break;
+			case CLONE:
+				add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", getAccessRestrictions().get(accessRestriction)));
+				break;
+			case VIEW:
+				add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", getAccessRestrictions().get(accessRestriction)));
+				break;
+			default:
+				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+			}
+
+			HttpServletRequest req = ((WebRequest) getRequestCycle().getRequest()).getHttpServletRequest();
+			StringBuilder sb = new StringBuilder();
+			sb.append(req.getScheme());
+			sb.append("://");
+			sb.append(req.getServerName());
+			if ((req.getScheme().equals("http") && req.getServerPort() != 80) || (req.getScheme().equals("https") && req.getServerPort() != 443)) {
+				sb.append(":" + req.getServerPort());
+			}
+			sb.append(Constants.GIT_SERVLET_PATH);
+			sb.append(repositoryName);
+			repositoryUrls.add(sb.toString());
+		} else {
 			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
 		}
-		add(new Label("repositoryCloneUrl", GitBlit.self().getCloneUrl(repositoryName)));
+		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(repositoryName));
+
+		add(new Label("repositoryCloneUrl", StringUtils.flattenStrings(repositoryUrls, "<br/>")).setEscapeModelStrings(false));
 
 		add(new LogPanel("commitsPanel", repositoryName, null, r, numberCommits, 0));
 		add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs));
@@ -113,9 +139,9 @@
 			commitAxis.setLabels(new String[] { "", String.valueOf((int) maxValue(metrics)) });
 			provider.addAxis(commitAxis);
 
-			provider.setLineStyles(new LineStyle[] {new LineStyle(2, 4, 0), new LineStyle(0, 4, 1)});	
+			provider.setLineStyles(new LineStyle[] { new LineStyle(2, 4, 0), new LineStyle(0, 4, 1) });
 			provider.addShapeMarker(new ShapeMarker(MarkerType.CIRCLE, Color.BLUE, 1, -1, 5));
-			
+
 			add(new Chart("commitsChart", provider));
 		} else {
 			add(WicketUtils.newBlankImage("commitsChart"));
diff --git a/src/com/gitblit/wicket/panels/BasePanel.java b/src/com/gitblit/wicket/panels/BasePanel.java
index 43cd126..6ebb8a7 100644
--- a/src/com/gitblit/wicket/panels/BasePanel.java
+++ b/src/com/gitblit/wicket/panels/BasePanel.java
@@ -2,8 +2,10 @@
 
 import java.util.TimeZone;
 
+import org.apache.wicket.AttributeModifier;
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
 
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
@@ -30,4 +32,22 @@
 			WicketUtils.setHtmlTooltip(component, getString("gb.searchForCommitter") + " " + value);
 		}
 	}
+
+	public class JavascriptEventConfirmation extends AttributeModifier {
+
+		private static final long serialVersionUID = 1L;
+
+		public JavascriptEventConfirmation(String event, String msg) {
+			super(event, true, new Model<String>(msg));
+		}
+
+		protected String newValue(final String currentValue, final String replacementValue) {
+			String prefix = "var conf = confirm('" + replacementValue + "'); " + "if (!conf) return false; ";
+			String result = prefix;
+			if (currentValue != null) {
+				result = prefix + currentValue;
+			}
+			return result;
+		}
+	}
 }
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html
index a066b58..a599d22 100644
--- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html
@@ -31,7 +31,7 @@
 	</wicket:fragment>
 	
 	<wicket:fragment wicket:id="repositoryAdminLinks">
-		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="renameRepository"><wicket:message key="gb.rename">[rename]</wicket:message></a> | <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>
+		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>
 	</wicket:fragment>
 
 	<wicket:fragment wicket:id="repositoryOwnerLinks">
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
index 6d05888..c141379 100644
--- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -1,5 +1,6 @@
 package com.gitblit.wicket.panels;
 
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -15,6 +16,7 @@
 import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
@@ -36,22 +38,21 @@
 import com.gitblit.wicket.pages.EditRepositoryPage;
 import com.gitblit.wicket.pages.SummaryPage;
 
-
 public class RepositoriesPanel extends BasePanel {
 
 	private static final long serialVersionUID = 1L;
-	
+
 	public RepositoriesPanel(String wicketId, final boolean showAdmin, final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
 		super(wicketId);
-		
+
 		final UserModel user = GitBlitWebSession.get().getUser();
 		List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
-		IDataProvider<RepositoryModel> dp;
-		
+		final IDataProvider<RepositoryModel> dp;
+
 		Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
 		adminLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
 		add(adminLinks.setVisible(showAdmin));
-		
+
 		if (GitBlit.self().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
 			Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
 			for (RepositoryModel model : models) {
@@ -69,24 +70,30 @@
 			List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
 			for (String root : roots) {
 				List<RepositoryModel> subModels = groups.get(root);
-				groupedModels.add(new GroupRepositoryModel(root + " (" + subModels.size() + ")"));
+				groupedModels.add(new GroupRepositoryModel(root, subModels.size()));
 				groupedModels.addAll(subModels);
 			}
-			dp = new ListDataProvider<RepositoryModel>(groupedModels);
+			dp = new RepositoriesProvider(groupedModels);
 		} else {
-			dp = new DataProvider(models);
+			dp = new SortableRepositoriesProvider(models);
 		}
 
 		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
 			private static final long serialVersionUID = 1L;
 			int counter = 0;
 
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
+
 			public void populateItem(final Item<RepositoryModel> item) {
 				final RepositoryModel entry = item.getModelObject();
 				if (entry instanceof GroupRepositoryModel) {
 					Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
 					item.add(row);
-					row.add(new Label("groupName", entry.name));
+					row.add(new Label("groupName", entry.toString()));
 					WicketUtils.setCssClass(item, "group");
 					return;
 				}
@@ -144,12 +151,30 @@
 				row.add(lastChangeLabel);
 				WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
 
-				boolean showOwner = user != null && user.getUsername().equalsIgnoreCase(entry.owner);				
+				boolean showOwner = user != null && user.getUsername().equalsIgnoreCase(entry.owner);
 				if (showAdmin) {
 					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryAdminLinks", this);
 					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
-					repositoryLinks.add(new BookmarkablePageLink<Void>("renameRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
-					repositoryLinks.add(new BookmarkablePageLink<Void>("deleteRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
+					Link<Void> deleteLink = new Link<Void>("deleteRepository") {
+
+						private static final long serialVersionUID = 1L;
+
+						@Override
+						public void onClick() {
+							if (GitBlit.self().deleteRepositoryModel(entry)) {
+								info(MessageFormat.format("Repository ''{0}'' deleted.", entry));
+								if (dp instanceof SortableRepositoriesProvider) {
+									((SortableRepositoriesProvider) dp).remove(entry);
+								} else {
+									((RepositoriesProvider) dp).remove(entry);
+								}
+							} else {
+								error(MessageFormat.format("Failed to delete repository ''{0}''!", entry));
+							}
+						}
+					};
+					deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format("Delete repository \"{0}\"?", entry)));
+					repositoryLinks.add(deleteLink);
 					row.add(repositoryLinks);
 				} else if (showOwner) {
 					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryOwnerLinks", this);
@@ -179,16 +204,24 @@
 			add(fragment);
 		}
 	}
-	
+
 	private class GroupRepositoryModel extends RepositoryModel {
 
 		private static final long serialVersionUID = 1L;
 
-		GroupRepositoryModel(String name) {
+		int count = 0;
+
+		GroupRepositoryModel(String name, int count) {
 			super(name, "", "", new Date(0));
+			this.count = count;
+		}
+
+		@Override
+		public String toString() {
+			return name + " (" + count + ")";
 		}
 	}
-	
+
 	protected enum SortBy {
 		repository, description, owner, date;
 	}
@@ -204,15 +237,71 @@
 		};
 	}
 
-	private class DataProvider extends SortableDataProvider<RepositoryModel> {
+	private class RepositoriesProvider extends ListDataProvider<RepositoryModel> {
+
+		private static final long serialVersionUID = 1L;
+
+		public RepositoriesProvider(List<RepositoryModel> list) {
+			super(list);
+		}
+
+		@Override
+		public List<RepositoryModel> getData() {
+			return super.getData();
+		}
+
+		public void remove(RepositoryModel model) {
+			int index = getData().indexOf(model);
+			RepositoryModel groupModel = null;
+			if (index == (getData().size() - 1)) {
+				// last element
+				if (index > 0) {
+					// previous element is group header, then this is last
+					// repository in group. remove group too.
+					if (getData().get(index - 1) instanceof GroupRepositoryModel) {
+						groupModel = getData().get(index - 1);
+					}
+				}
+			} else if (index < (getData().size() - 1)) {
+				// not last element. check next element for group match.
+				if (getData().get(index - 1) instanceof GroupRepositoryModel && getData().get(index + 1) instanceof GroupRepositoryModel) {
+					// repository is sandwiched by group headers so this
+					// repository is the only element in the group. remove
+					// group.
+					groupModel = getData().get(index - 1);
+				}
+			}
+
+			if (groupModel == null) {
+				// Find the group and decrement the count
+				for (int i = index; i >= 0; i--) {
+					if (getData().get(i) instanceof GroupRepositoryModel) {
+						((GroupRepositoryModel) getData().get(i)).count--;
+						break;
+					}
+				}
+			} else {
+				// Remove the group header
+				getData().remove(groupModel);
+			}
+
+			getData().remove(model);
+		}
+	}
+
+	private class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {
 		private static final long serialVersionUID = 1L;
 		private List<RepositoryModel> list = null;
 
-		protected DataProvider(List<RepositoryModel> list) {
+		protected SortableRepositoriesProvider(List<RepositoryModel> list) {
 			this.list = list;
 			setSort(SortBy.date.name(), false);
 		}
 
+		public void remove(RepositoryModel model) {
+			list.remove(model);
+		}
+
 		@Override
 		public int size() {
 			if (list == null)
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.java b/src/com/gitblit/wicket/panels/UsersPanel.java
index 1721272..55671ec 100644
--- a/src/com/gitblit/wicket/panels/UsersPanel.java
+++ b/src/com/gitblit/wicket/panels/UsersPanel.java
@@ -1,6 +1,10 @@
 package com.gitblit.wicket.panels;
 
+import java.text.MessageFormat;
+import java.util.List;
+
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.markup.repeater.Item;
 import org.apache.wicket.markup.repeater.data.DataView;
@@ -10,7 +14,6 @@
 import com.gitblit.wicket.LinkPanel;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.pages.EditUserPage;
-import com.gitblit.wicket.pages.RepositoriesPage;
 
 public class UsersPanel extends BasePanel {
 
@@ -22,10 +25,17 @@
 		Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
 		adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
 		add(adminLinks.setVisible(showAdmin));
-		
-		DataView<String> usersView = new DataView<String>("userRow", new ListDataProvider<String>(GitBlit.self().getAllUsernames())) {
+
+		final List<String> usernames = GitBlit.self().getAllUsernames();
+		DataView<String> usersView = new DataView<String>("userRow", new ListDataProvider<String>(usernames)) {
 			private static final long serialVersionUID = 1L;
 			private int counter = 0;
+			
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
 
 			public void populateItem(final Item<String> item) {
 				final String entry = item.getModelObject();
@@ -34,7 +44,22 @@
 				item.add(editLink);
 				Fragment userLinks = new Fragment("userLinks", "userAdminLinks", this);
 				userLinks.add(new BookmarkablePageLink<Void>("editUser", EditUserPage.class, WicketUtils.newUsernameParameter(entry)));
-				userLinks.add(new BookmarkablePageLink<Void>("deleteUser", RepositoriesPage.class, WicketUtils.newUsernameParameter(entry)).setEnabled(false));
+				Link<Void> deleteLink = new Link<Void>("deleteUser") {
+
+					private static final long serialVersionUID = 1L;
+
+					@Override
+					public void onClick() {
+						if (GitBlit.self().deleteUser(entry)) {
+							usernames.remove(entry);
+							info(MessageFormat.format("User ''{0}'' deleted.", entry));
+						} else {
+							error(MessageFormat.format("Failed to delete user ''{0}''!", entry));
+						}
+					}
+				};
+				deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format("Delete user \"{0}\"?", entry)));
+				userLinks.add(deleteLink);
 				item.add(userLinks);
 
 				WicketUtils.setAlternatingBackground(item, counter);
diff --git a/src/com/gitblit/wicket/resources/gitblt.png b/src/com/gitblit/wicket/resources/gitblt.png
deleted file mode 100644
index 7535bc3..0000000
--- a/src/com/gitblit/wicket/resources/gitblt.png
+++ /dev/null
Binary files differ
diff --git a/src/com/gitblit/wicket/resources/gitblt3.png b/src/com/gitblit/wicket/resources/gitblt3.png
deleted file mode 100644
index f178d03..0000000
--- a/src/com/gitblit/wicket/resources/gitblt3.png
+++ /dev/null
Binary files differ

--
Gitblit v1.9.1