Delete/Rename repos & user. Edit link. Dropped crypt. Other git urls.
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,14 +8,16 @@
 

 	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();

 	

 	List<String> getUsernamesForRole(String role);

@@ -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

@@ -62,12 +60,6 @@
 	}

 

 	@Override

-	public UserModel authenticate(char[] cookie) {

-		// TODO cookie login

-		return null;

-	}

-

-	@Override

 	public UserModel getUserModel(String username) {

 		UserIdentity identity = _users.get(username);

 		if (identity == null) {

@@ -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;

@@ -28,8 +24,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,29 +60,12 @@
 				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

 				setResponsePage(getApplication().getHomePage());

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,16 +92,29 @@
 		// 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);

 		DataView<String> extrasView = new DataView<String>("extra", extrasDp) {

 			private static final long serialVersionUID = 1L;

@@ -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