James Moger
2011-05-24 8a2e9c363346ef5bf48c8eba09cb8afa46fabeeb
Delete/Rename repos & user. Edit link. Dropped crypt. Other git urls.
18 files modified
2 files deleted
494 ■■■■■ changed files
distrib/gitblit.properties 8 ●●●●● patch | view | raw | blame | history
src/com/gitblit/GitBlit.java 103 ●●●●● patch | view | raw | blame | history
src/com/gitblit/ILoginService.java 7 ●●●●● patch | view | raw | blame | history
src/com/gitblit/JettyLoginService.java 29 ●●●●● patch | view | raw | blame | history
src/com/gitblit/utils/StringUtils.java 6 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/LoginPage.java 27 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/RepositoryPage.java 22 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/models/RepositoryModel.java 7 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/models/UserModel.java 10 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditRepositoryPage.java 12 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.java 16 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/RepositoriesPage.java 1 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/SummaryPage.java 64 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/BasePanel.java 20 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoriesPanel.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoriesPanel.java 125 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/UsersPanel.java 33 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/resources/gitblt.png patch | view | raw | blame | history
src/com/gitblit/wicket/resources/gitblt3.png patch | view | raw | blame | history
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.
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;
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);
}
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);
        }
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();
    }
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
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)));
                }
            }
        };
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;
    }
}
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;
    }
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())) {
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);
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
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>
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"));
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;
        }
    }
}
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">
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)
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);
src/com/gitblit/wicket/resources/gitblt.png
Binary files differ
src/com/gitblit/wicket/resources/gitblt3.png
Binary files differ