From 1e1b85270f93b3bca624c99b478f3a9a23be2395 Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Sat, 29 Sep 2012 23:40:46 -0400 Subject: [PATCH] Preliminary implementation of server-side forking (issue 137) --- src/com/gitblit/utils/StringUtils.java | 29 + src/com/gitblit/ConfigUserService.java | 4 src/com/gitblit/wicket/pages/ProjectsPage.java | 8 src/com/gitblit/wicket/panels/RepositoriesPanel.java | 23 src/com/gitblit/models/UserModel.java | 29 + src/com/gitblit/wicket/pages/RepositoryPage.java | 123 ++++ src/com/gitblit/wicket/pages/EditUserPage.html | 5 src/com/gitblit/wicket/pages/ProjectPage.html | 70 -- src/com/gitblit/GitBlit.java | 187 ++++++ src/com/gitblit/wicket/GitBlitWebApp.properties | 16 docs/04_releases.mkd | 13 src/com/gitblit/wicket/pages/RootPage.java | 9 src/com/gitblit/wicket/pages/RepositoryPage.html | 34 + src/com/gitblit/wicket/panels/RepositoriesPanel.html | 2 src/com/gitblit/wicket/pages/EditRepositoryPage.html | 4 src/com/gitblit/Constants.java | 2 src/com/gitblit/utils/JGitUtils.java | 14 src/com/gitblit/wicket/pages/UserPage.java | 146 +++++ src/com/gitblit/wicket/pages/GitSearchPage.java | 2 src/com/gitblit/wicket/pages/EditRepositoryPage.java | 1 src/com/gitblit/wicket/panels/GravatarImage.java | 2 src/com/gitblit/wicket/pages/ForksPage.html | 24 src/com/gitblit/wicket/pages/ProjectPage.java | 152 ----- src/com/gitblit/wicket/pages/EditUserPage.java | 1 src/com/gitblit/wicket/pages/UserPage.html | 44 + src/com/gitblit/SyndicationServlet.java | 2 .project | 46 src/com/gitblit/FileUserService.java | 5 src/com/gitblit/wicket/pages/ForksPage.java | 133 +++++ src/com/gitblit/wicket/pages/LogPage.java | 2 src/com/gitblit/wicket/panels/HistoryPanel.java | 4 src/com/gitblit/utils/ActivityUtils.java | 2 src/com/gitblit/wicket/pages/BasePage.java | 9 src/com/gitblit/wicket/panels/SearchPanel.java | 5 src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html | 79 +++ src/com/gitblit/models/RepositoryModel.java | 49 + src/com/gitblit/wicket/panels/LogPanel.java | 4 src/com/gitblit/models/ProjectModel.java | 4 src/com/gitblit/wicket/pages/HistoryPage.java | 2 src/com/gitblit/wicket/GitBlitWebApp.java | 4 docs/05_roadmap.mkd | 1 resources/gitblit.css | 46 + src/com/gitblit/wicket/pages/SummaryPage.java | 2 src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java | 199 +++++++ 44 files changed, 1,238 insertions(+), 304 deletions(-) diff --git a/.project b/.project index 65c86e8..90c747b 100644 --- a/.project +++ b/.project @@ -1,23 +1,23 @@ -<?xml version="1.0" encoding="UTF-8"?> -<projectDescription> - <name>gitblit</name> - <comment></comment> - <projects> - </projects> - <buildSpec> - <buildCommand> - <name>org.eclipse.jdt.core.javabuilder</name> - <arguments> - </arguments> - </buildCommand> - <buildCommand> - <name>net.sf.eclipsecs.core.CheckstyleBuilder</name> - <arguments> - </arguments> - </buildCommand> - </buildSpec> - <natures> - <nature>org.eclipse.jdt.core.javanature</nature> - <nature>net.sf.eclipsecs.core.CheckstyleNature</nature> - </natures> -</projectDescription> +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>Gitblit</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + <buildCommand> + <name>net.sf.eclipsecs.core.CheckstyleBuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + <nature>net.sf.eclipsecs.core.CheckstyleNature</nature> + </natures> +</projectDescription> diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd index 454ce65..30582d5 100644 --- a/docs/04_releases.mkd +++ b/docs/04_releases.mkd @@ -17,10 +17,15 @@ #### additions -- added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135) -- delete branch feature (issue 121, Github/ajermakovics) -- added line links to blob view at the expense of zebra striping (issue 130) -- added RedmineUserService (github/mallowlabs) +- Added simple project pages. A project is a subfolder off the *git.repositoriesFolder*. +- Added support for personal repositories. This builds on the simple project pages. +Personal repositories are stored in *git.repositoriesFolder*/*~username*. Each user with personal repositories will have a user page, something like the GitHub profile page. Personal repositories have all the same features as common repositories. +- Added support for server-side forking of a repository to a personal repository (issue 137) +In order to fork a repository to a personal clone, the user account must have the *fork* permission **and** the repository must *allow forks*. The clone inherits the access restrictions of its origin. i.e. if Team A has access to the origin repository, then by default Team A also has access to the fork. This is to facilitate collaboration. However, the fork owner may change access to the fork and add/remove users/teams, etc as required. +- Added support for X-Forwarded-Context for Apache subdomain proxy configurations (issue 135) +- Delete branch feature (issue 121, Github/ajermakovics) +- Added line links to blob view at the expense of zebra striping (issue 130) +- Added RedmineUserService (github/mallowlabs) #### changes diff --git a/docs/05_roadmap.mkd b/docs/05_roadmap.mkd index 3238f73..562cc7e 100644 --- a/docs/05_roadmap.mkd +++ b/docs/05_roadmap.mkd @@ -26,7 +26,6 @@ ### IDEAS * Gitblit: Re-use the EGit branch visualization table cell renderer as some sort of servlet -* Gitblit: Support personal repositories (~username/repo) * Gitblit: diff should highlight inserted/removed fragment compared to original line * Gitblit: implement branch permission controls as Groovy pre-receive script. *Maintain permissions text file similar to a gitolite configuration file or svn authz file.* diff --git a/resources/gitblit.css b/resources/gitblit.css index fc948b6..66387d1 100644 --- a/resources/gitblit.css +++ b/resources/gitblit.css @@ -20,6 +20,11 @@ outline: none; } +[class^="icon-"], [class*=" icon-"] a i { + /* override for a links that look like bootstrap buttons */ + vertical-align: text-bottom; +} + hr { margin-top: 10px; margin-bottom: 10px; @@ -127,6 +132,47 @@ font-weight: bold; } +.pageTitle { + color: #888; + font-size: 18px; + line-height: 27px; +} +.pageTitle .project, .pageTitle .repository { + font-family: Helvetica, arial, freesans, clean, sans-serif; + font-size: 22px; +} + +.pageTitle .controls { + font-size: 12px; +} + +.pageTitle .repository { + font-weight: bold; +} + +.originRepository { + font-family: Helvetica, arial, freesans, clean, sans-serif; + color: #888; + font-size: 12px; + line-height: 14px; + margin: 0px; +} + +.forkSource, .forkEntry { + color: #888; +} + +.forkSource { + font-size: 18px; + line-height: 20px; + padding: 5px 0px; +} + +.forkEntry { + font-size: 14px; + padding: 2px 0px; +} + div.page_footer { clear: both; height: 17px; diff --git a/src/com/gitblit/ConfigUserService.java b/src/com/gitblit/ConfigUserService.java index faad691..f526835 100644 --- a/src/com/gitblit/ConfigUserService.java +++ b/src/com/gitblit/ConfigUserService.java @@ -750,6 +750,9 @@ if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } + if (model.canFork) { + roles.add(Constants.FORK_ROLE); + } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } @@ -858,6 +861,7 @@ Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList( USER, username, ROLE))); user.canAdmin = roles.contains(Constants.ADMIN_ROLE); + user.canFork = roles.contains(Constants.FORK_ROLE); user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE); // repository memberships diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 55d4980..c53119b 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -41,6 +41,8 @@ public static final String JGIT_VERSION = "JGit 2.1.0 (201209190230-r)"; public static final String ADMIN_ROLE = "#admin"; + + public static final String FORK_ROLE = "#fork"; public static final String NOT_FEDERATED_ROLE = "#notfederated"; diff --git a/src/com/gitblit/FileUserService.java b/src/com/gitblit/FileUserService.java index 7705dfd..40bc3f6 100644 --- a/src/com/gitblit/FileUserService.java +++ b/src/com/gitblit/FileUserService.java @@ -234,6 +234,8 @@ // Permissions if (role.equalsIgnoreCase(Constants.ADMIN_ROLE)) { model.canAdmin = true; + } else if (role.equalsIgnoreCase(Constants.FORK_ROLE)) { + model.canFork = true; } else if (role.equalsIgnoreCase(Constants.NOT_FEDERATED_ROLE)) { model.excludeFromFederation = true; } @@ -283,6 +285,9 @@ if (model.canAdmin) { roles.add(Constants.ADMIN_ROLE); } + if (model.canFork) { + roles.add(Constants.FORK_ROLE); + } if (model.excludeFromFederation) { roles.add(Constants.NOT_FEDERATED_ROLE); } diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java index c758654..699bbac 100644 --- a/src/com/gitblit/GitBlit.java +++ b/src/com/gitblit/GitBlit.java @@ -22,6 +22,8 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; import java.text.MessageFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -747,6 +749,14 @@ private void addToCachedRepositoryList(String name, RepositoryModel model) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { repositoryListCache.put(name, model); + + // update the fork origin repository with this repository clone + if (!StringUtils.isEmpty(model.originRepository)) { + if (repositoryListCache.containsKey(model.originRepository)) { + RepositoryModel origin = repositoryListCache.get(model.originRepository); + origin.addFork(name); + } + } } } @@ -754,12 +764,13 @@ * Removes the repository from the list of cached repositories. * * @param name + * @return the model being removed */ - private void removeFromCachedRepositoryList(String name) { + private RepositoryModel removeFromCachedRepositoryList(String name) { if (StringUtils.isEmpty(name)) { - return; + return null; } - repositoryListCache.remove(name); + return repositoryListCache.remove(name); } /** @@ -988,7 +999,7 @@ if (model == null) { return null; } - addToCachedRepositoryList(repositoryName, model); + addToCachedRepositoryList(repositoryName, model); return model; } @@ -1034,7 +1045,7 @@ * @return project config map */ private Map<String, ProjectModel> getProjectConfigs() { - if (projectConfigs.isOutdated()) { + if (projectCache.isEmpty() || projectConfigs.isOutdated()) { try { projectConfigs.load(); @@ -1077,9 +1088,10 @@ * Returns a list of project models for the user. * * @param user + * @param includeUsers * @return list of projects that are accessible to the user */ - public List<ProjectModel> getProjectModels(UserModel user) { + public List<ProjectModel> getProjectModels(UserModel user, boolean includeUsers) { Map<String, ProjectModel> configs = getProjectConfigs(); // per-user project lists, this accounts for security and visibility @@ -1104,10 +1116,25 @@ } // sort projects, root project first - List<ProjectModel> projects = new ArrayList<ProjectModel>(map.values()); - Collections.sort(projects); - projects.remove(map.get("")); - projects.add(0, map.get("")); + List<ProjectModel> projects; + if (includeUsers) { + // all projects + projects = new ArrayList<ProjectModel>(map.values()); + Collections.sort(projects); + projects.remove(map.get("")); + projects.add(0, map.get("")); + } else { + // all non-user projects + projects = new ArrayList<ProjectModel>(); + ProjectModel root = map.remove(""); + for (ProjectModel model : map.values()) { + if (!model.isUserProject()) { + projects.add(model); + } + } + Collections.sort(projects); + projects.add(0, root); + } return projects; } @@ -1119,7 +1146,7 @@ * @return a project model, or null if it does not exist */ public ProjectModel getProjectModel(String name, UserModel user) { - for (ProjectModel project : getProjectModels(user)) { + for (ProjectModel project : getProjectModels(user, true)) { if (project.name.equalsIgnoreCase(name)) { return project; } @@ -1137,15 +1164,37 @@ Map<String, ProjectModel> configs = getProjectConfigs(); ProjectModel project = configs.get(name.toLowerCase()); if (project == null) { - return null; - } - // clone the object - project = DeepCopier.copy(project); - String folder = name.toLowerCase() + "/"; - for (String repository : getRepositoryList()) { - if (repository.toLowerCase().startsWith(folder)) { - project.addRepository(repository); + project = new ProjectModel(name); + if (name.length() > 0 && name.charAt(0) == '~') { + UserModel user = getUserModel(name.substring(1)); + if (user != null) { + project.title = user.getDisplayName(); + project.description = "personal repositories"; + } } + } else { + // clone the object + project = DeepCopier.copy(project); + } + if (StringUtils.isEmpty(name)) { + // get root repositories + for (String repository : getRepositoryList()) { + if (repository.indexOf('/') == -1) { + project.addRepository(repository); + } + } + } else { + // get repositories in subfolder + String folder = name.toLowerCase() + "/"; + for (String repository : getRepositoryList()) { + if (repository.toLowerCase().startsWith(folder)) { + project.addRepository(repository); + } + } + } + if (project.repositories.size() == 0) { + // no repositories == no project + return null; } return project; } @@ -1189,18 +1238,26 @@ model.hasCommits = JGitUtils.hasCommits(r); model.lastChange = JGitUtils.getLastChange(r); model.isBare = r.isBare(); + if (repositoryName.indexOf('/') == -1) { + model.projectPath = ""; + } else { + model.projectPath = repositoryName.substring(0, repositoryName.indexOf('/')); + } StoredConfig config = r.getConfig(); + boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url")); + if (config != null) { model.description = getConfig(config, "description", ""); model.owner = getConfig(config, "owner", ""); model.useTickets = getConfig(config, "useTickets", false); model.useDocs = getConfig(config, "useDocs", false); + model.allowForks = getConfig(config, "allowForks", true); model.accessRestriction = AccessRestrictionType.fromName(getConfig(config, "accessRestriction", settings.getString(Keys.git.defaultAccessRestriction, null))); model.authorizationControl = AuthorizationControl.fromName(getConfig(config, "authorizationControl", settings.getString(Keys.git.defaultAuthorizationControl, null))); - model.showRemoteBranches = getConfig(config, "showRemoteBranches", false); + model.showRemoteBranches = getConfig(config, "showRemoteBranches", hasOrigin); model.isFrozen = getConfig(config, "isFrozen", false); model.showReadme = getConfig(config, "showReadme", false); model.skipSizeCalculation = getConfig(config, "skipSizeCalculation", false); @@ -1229,6 +1286,23 @@ model.HEAD = JGitUtils.getHEADRef(r); model.availableRefs = JGitUtils.getAvailableHeadTargets(r); r.close(); + + if (model.origin != null && model.origin.startsWith("file://")) { + // repository was cloned locally... perhaps as a fork + try { + File folder = new File(new URI(model.origin)); + String originRepo = com.gitblit.utils.FileUtils.getRelativePath(getRepositoriesFolder(), folder); + if (!StringUtils.isEmpty(originRepo)) { + // ensure origin still exists + File repoFolder = new File(getRepositoriesFolder(), originRepo); + if (repoFolder.exists()) { + model.originRepository = originRepo; + } + } + } catch (URISyntaxException e) { + logger.error("Failed to determine fork for " + model, e); + } + } return model; } @@ -1425,9 +1499,27 @@ "Failed to rename repository permissions ''{0}'' to ''{1}''.", repositoryName, repository.name)); } + + // rename fork origins in their configs + if (!ArrayUtils.isEmpty(repository.forks)) { + for (String fork : repository.forks) { + Repository rf = getRepository(fork); + try { + StoredConfig config = rf.getConfig(); + String origin = config.getString("remote", "origin", "url"); + origin = origin.replace(repositoryName, repository.name); + config.setString("remote", "origin", "url", origin); + config.save(); + } catch (Exception e) { + logger.error("Failed to update repository fork config for " + fork, e); + } + rf.close(); + } + } // clear the cache clearRepositoryMetadataCache(repositoryName); + repository.resetDisplayName(); } // load repository @@ -1483,6 +1575,7 @@ config.setString(Constants.CONFIG_GITBLIT, null, "owner", repository.owner); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useTickets", repository.useTickets); config.setBoolean(Constants.CONFIG_GITBLIT, null, "useDocs", repository.useDocs); + config.setBoolean(Constants.CONFIG_GITBLIT, null, "allowForks", repository.allowForks); config.setString(Constants.CONFIG_GITBLIT, null, "accessRestriction", repository.accessRestriction.name()); config.setString(Constants.CONFIG_GITBLIT, null, "authorizationControl", repository.authorizationControl.name()); config.setBoolean(Constants.CONFIG_GITBLIT, null, "showRemoteBranches", repository.showRemoteBranches); @@ -1558,7 +1651,11 @@ closeRepository(repositoryName); // clear the repository cache clearRepositoryMetadataCache(repositoryName); - removeFromCachedRepositoryList(repositoryName); + + RepositoryModel model = removeFromCachedRepositoryList(repositoryName); + if (!ArrayUtils.isEmpty(model.forks)) { + resetRepositoryListCache(); + } File folder = new File(repositoriesFolder, repositoryName); if (folder.exists() && folder.isDirectory()) { @@ -2423,4 +2520,52 @@ scheduledExecutor.shutdownNow(); luceneExecutor.close(); } + + /** + * Creates a personal fork of the specified repository. The clone is view + * restricted by default and the owner of the source repository is given + * access to the clone. + * + * @param repository + * @param user + * @return true, if successful + */ + public boolean fork(RepositoryModel repository, UserModel user) { + String cloneName = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(repository.name))); + String fromUrl = MessageFormat.format("file://{0}/{1}", repositoriesFolder.getAbsolutePath(), repository.name); + try { + // clone the repository + JGitUtils.cloneRepository(repositoriesFolder, cloneName, fromUrl, true, null); + + // create a Gitblit repository model for the clone + RepositoryModel cloneModel = repository.cloneAs(cloneName); + cloneModel.owner = user.username; + updateRepositoryModel(cloneName, cloneModel, false); + + if (AuthorizationControl.NAMED.equals(cloneModel.authorizationControl)) { + // add the owner of the source repository to the clone's access list + if (!StringUtils.isEmpty(repository.owner)) { + UserModel owner = getUserModel(repository.owner); + if (owner != null) { + owner.repositories.add(cloneName); + updateUserModel(owner.username, owner, false); + } + } + + // inherit origin's access lists + List<String> users = getRepositoryUsers(repository); + setRepositoryUsers(cloneModel, users); + + List<String> teams = getRepositoryTeams(repository); + setRepositoryTeams(cloneModel, teams); + } + + // add this clone to the cached model + addToCachedRepositoryList(cloneModel.name, cloneModel); + return true; + } catch (Exception e) { + logger.error("failed to fork", e); + } + return false; + } } diff --git a/src/com/gitblit/SyndicationServlet.java b/src/com/gitblit/SyndicationServlet.java index 4c542b6..a36f583 100644 --- a/src/com/gitblit/SyndicationServlet.java +++ b/src/com/gitblit/SyndicationServlet.java @@ -227,7 +227,7 @@ commits = JGitUtils.searchRevlogs(repository, objectId, searchString, searchType, offset, length); } - Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository); + Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository, model.showRemoteBranches); // convert RevCommit to SyndicatedEntryModel for (RevCommit commit : commits) { diff --git a/src/com/gitblit/models/ProjectModel.java b/src/com/gitblit/models/ProjectModel.java index bc35903..189a409 100644 --- a/src/com/gitblit/models/ProjectModel.java +++ b/src/com/gitblit/models/ProjectModel.java @@ -53,6 +53,10 @@ this.title = ""; this.description = ""; } + + public boolean isUserProject() { + return name.charAt(0) == '~'; + } public boolean hasRepository(String name) { return repositories.contains(name.toLowerCase()); diff --git a/src/com/gitblit/models/RepositoryModel.java b/src/com/gitblit/models/RepositoryModel.java index 2719663..44aba1d 100644 --- a/src/com/gitblit/models/RepositoryModel.java +++ b/src/com/gitblit/models/RepositoryModel.java @@ -20,6 +20,8 @@ import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AuthorizationControl; @@ -68,7 +70,11 @@ public List<String> postReceiveScripts; public List<String> mailingLists; public Map<String, String> customFields; + public String projectPath; private String displayName; + public boolean allowForks; + public Set<String> forks; + public String originRepository; public RepositoryModel() { this("", "", "", new Date(0)); @@ -97,6 +103,24 @@ } return localBranches; } + + public void addFork(String repository) { + if (forks == null) { + forks = new TreeSet<String>(); + } + forks.add(repository); + } + + public void removeFork(String repository) { + if (forks == null) { + return; + } + forks.remove(repository); + } + + public void resetDisplayName() { + displayName = null; + } @Override public String toString() { @@ -110,4 +134,29 @@ public int compareTo(RepositoryModel o) { return StringUtils.compareRepositoryNames(name, o.name); } + + public boolean isPersonalRepository() { + return !StringUtils.isEmpty(projectPath) && projectPath.charAt(0) == '~'; + } + + public boolean isUsersPersonalRepository(String username) { + return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase("~" + username); + } + + public RepositoryModel cloneAs(String cloneName) { + RepositoryModel clone = new RepositoryModel(); + clone.name = cloneName; + clone.description = description; + clone.accessRestriction = accessRestriction; + clone.authorizationControl = authorizationControl; + clone.federationStrategy = federationStrategy; + clone.showReadme = showReadme; + clone.showRemoteBranches = false; + clone.allowForks = false; + clone.useDocs = useDocs; + clone.useTickets = useTickets; + clone.skipSizeCalculation = skipSizeCalculation; + clone.skipSummaryMetrics = skipSummaryMetrics; + return clone; + } } \ No newline at end of file diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java index 8349bab..0ede878 100644 --- a/src/com/gitblit/models/UserModel.java +++ b/src/com/gitblit/models/UserModel.java @@ -20,6 +20,7 @@ import java.util.HashSet; import java.util.Set; +import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.AuthorizationControl; import com.gitblit.utils.StringUtils; @@ -42,6 +43,7 @@ public String displayName; public String emailAddress; public boolean canAdmin; + public boolean canFork; public boolean excludeFromFederation; public final Set<String> repositories = new HashSet<String>(); public final Set<TeamModel> teams = new HashSet<TeamModel>(); @@ -83,6 +85,33 @@ } return false; } + + public boolean canForkRepository(RepositoryModel repository) { + if (canAdmin) { + return true; + } + if (!canFork) { + // user has been prohibited from forking + return false; + } + if (!isAuthenticated) { + // unauthenticated user model + return false; + } + if (("~" + username).equalsIgnoreCase(repository.projectPath)) { + // this repository is already a personal repository + return false; + } + if (!repository.allowForks) { + // repository prohibits forks + return false; + } + if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) { + return canAccessRepository(repository); + } + // repository is not clone-restricted + return true; + } public boolean hasRepository(String name) { return repositories.contains(name.toLowerCase()); diff --git a/src/com/gitblit/utils/ActivityUtils.java b/src/com/gitblit/utils/ActivityUtils.java index 02a9924..e515994 100644 --- a/src/com/gitblit/utils/ActivityUtils.java +++ b/src/com/gitblit/utils/ActivityUtils.java @@ -94,7 +94,7 @@ branches.add(objectId); } Map<ObjectId, List<RefModel>> allRefs = JGitUtils - .getAllRefs(repository); + .getAllRefs(repository, model.showRemoteBranches); for (String branch : branches) { String shortName = branch; diff --git a/src/com/gitblit/utils/JGitUtils.java b/src/com/gitblit/utils/JGitUtils.java index c5cd1c3..050b591 100644 --- a/src/com/gitblit/utils/JGitUtils.java +++ b/src/com/gitblit/utils/JGitUtils.java @@ -1369,9 +1369,23 @@ * @return all refs grouped by their referenced object id */ public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) { + return getAllRefs(repository, true); + } + + /** + * Returns all refs grouped by their associated object id. + * + * @param repository + * @param includeRemoteRefs + * @return all refs grouped by their referenced object id + */ + public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository, boolean includeRemoteRefs) { List<RefModel> list = getRefs(repository, org.eclipse.jgit.lib.RefDatabase.ALL, true, -1); Map<ObjectId, List<RefModel>> refs = new HashMap<ObjectId, List<RefModel>>(); for (RefModel ref : list) { + if (!includeRemoteRefs && ref.getName().startsWith(Constants.R_REMOTES)) { + continue; + } ObjectId objectid = ref.getReferencedObjectId(); if (!refs.containsKey(objectid)) { refs.put(objectid, new ArrayList<RefModel>()); diff --git a/src/com/gitblit/utils/StringUtils.java b/src/com/gitblit/utils/StringUtils.java index e440790..0711338 100644 --- a/src/com/gitblit/utils/StringUtils.java +++ b/src/com/gitblit/utils/StringUtils.java @@ -367,7 +367,7 @@ * @return the first invalid character found or null if string is acceptable */ public static Character findInvalidCharacter(String name) { - char[] validChars = { '/', '.', '_', '-' }; + char[] validChars = { '/', '.', '_', '-', '~' }; for (char c : name.toCharArray()) { if (!Character.isLetterOrDigit(c)) { boolean ok = false; @@ -660,4 +660,31 @@ } return input; } + + /** + * Returns the first path element of a path string. If no path separator is + * found in the path, an empty string is returned. + * + * @param path + * @return the first element in the path + */ + public static String getFirstPathElement(String path) { + if (path.indexOf('/') > -1) { + return path.substring(0, path.indexOf('/')).trim(); + } + return ""; + } + + /** + * Returns the last path element of a path string + * + * @param path + * @return the last element in the path + */ + public static String getLastPathElement(String path) { + if (path.indexOf('/') > -1) { + return path.substring(path.lastIndexOf('/') + 1); + } + return path; + } } \ No newline at end of file diff --git a/src/com/gitblit/wicket/GitBlitWebApp.java b/src/com/gitblit/wicket/GitBlitWebApp.java index 507de15..b691cea 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.java +++ b/src/com/gitblit/wicket/GitBlitWebApp.java @@ -34,6 +34,7 @@ import com.gitblit.wicket.pages.CommitPage; import com.gitblit.wicket.pages.DocsPage; import com.gitblit.wicket.pages.FederationRegistrationPage; +import com.gitblit.wicket.pages.ForksPage; import com.gitblit.wicket.pages.GitSearchPage; import com.gitblit.wicket.pages.GravatarProfilePage; import com.gitblit.wicket.pages.HistoryPage; @@ -53,6 +54,7 @@ import com.gitblit.wicket.pages.TicketPage; import com.gitblit.wicket.pages.TicketsPage; import com.gitblit.wicket.pages.TreePage; +import com.gitblit.wicket.pages.UserPage; import com.gitblit.wicket.pages.UsersPage; public class GitBlitWebApp extends WebApplication { @@ -116,6 +118,8 @@ mount("/lucene", LuceneSearchPage.class); mount("/project", ProjectPage.class, "p"); mount("/projects", ProjectsPage.class); + mount("/user", UserPage.class, "user"); + mount("/forks", ForksPage.class, "r"); } private void mount(String location, Class<? extends WebPage> clazz, String... parameters) { diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index c427dd3..50c43fe 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -318,4 +318,18 @@ gb.projects = projects gb.project = project gb.allProjects = all projects -gb.copyToClipboard = copy to clipboard \ No newline at end of file +gb.copyToClipboard = copy to clipboard +gb.fork = fork +gb.forks = forks +gb.forkRepository = fork {0}? +gb.repositoryForked = {0} has been forked +gb.repositoryForkFailed= failed to fork {1} +gb.personalRepositories = personal repositories +gb.allowForks = allow forks +gb.allowForksDescription = allow authorized users to fork this repository +gb.forkedFrom = forked from +gb.canFork = can fork +gb.canForkDescription = user is permitted to fork authorized repositories +gb.myFork = view my fork +gb.forksProhibited = forks prohibited +gb.forksProhibitedWarning = this repository forbids forks \ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java index f9f90b0..00d9677 100644 --- a/src/com/gitblit/wicket/pages/BasePage.java +++ b/src/com/gitblit/wicket/pages/BasePage.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; -import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashMap; @@ -36,6 +35,7 @@ import org.apache.wicket.MarkupContainer; import org.apache.wicket.PageParameters; import org.apache.wicket.RedirectToUrlException; +import org.apache.wicket.RequestCycle; import org.apache.wicket.RestartResponseException; import org.apache.wicket.markup.html.CSSPackageResource; import org.apache.wicket.markup.html.WebPage; @@ -63,7 +63,6 @@ import com.gitblit.utils.StringUtils; import com.gitblit.utils.TimeUtils; import com.gitblit.wicket.GitBlitWebSession; -import com.gitblit.wicket.PageRegistration.DropDownMenuItem; import com.gitblit.wicket.WicketUtils; import com.gitblit.wicket.panels.LinkPanel; @@ -235,9 +234,9 @@ return req.getServerName(); } - protected String getRepositoryUrl(RepositoryModel repository) { + public static String getRepositoryUrl(RepositoryModel repository) { StringBuilder sb = new StringBuilder(); - sb.append(WicketUtils.getGitblitURL(getRequestCycle().getRequest())); + sb.append(WicketUtils.getGitblitURL(RequestCycle.get().getRequest())); sb.append(Constants.GIT_PATH); sb.append(repository.name); @@ -252,7 +251,7 @@ protected List<ProjectModel> getProjectModels() { final UserModel user = GitBlitWebSession.get().getUser(); - List<ProjectModel> projects = GitBlit.self().getProjectModels(user); + List<ProjectModel> projects = GitBlit.self().getProjectModels(user, true); return projects; } diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/com/gitblit/wicket/pages/EditRepositoryPage.html index 2bb5776..6809c2d 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.html +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.html @@ -34,10 +34,12 @@ </wicket:container> </td></tr> <tr><th colspan="2"><hr/></th></tr> + <tr><th><wicket:message key="gb.allowForks"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="allowForks" tabindex="17" /> <span class="help-inline"><wicket:message key="gb.allowForksDescription"></wicket:message></span></label></td></tr> + <tr><th colspan="2"><hr/></th></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedUsers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.permittedTeams"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr> <tr><td colspan="2"><h3><wicket:message key="gb.federation"></wicket:message> <small><wicket:message key="gb.federationRepositoryDescription"></wicket:message></small></h3></td></tr> - <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="17" /></td></tr> + <tr><th><wicket:message key="gb.federationStrategy"></wicket:message></th><td class="edit"><select class="span4" wicket:id="federationStrategy" tabindex="18" /></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.federationSets"></wicket:message></th><td style="padding:2px;"><span wicket:id="federationSets"></span></td></tr> <tr><td colspan="2"><h3><wicket:message key="gb.search"></wicket:message> <small><wicket:message key="gb.indexedBranchesDescription"></wicket:message></small></h3></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.indexedBranches"></wicket:message></th><td style="padding:2px;"><span wicket:id="indexedBranches"></span></td></tr> diff --git a/src/com/gitblit/wicket/pages/EditRepositoryPage.java b/src/com/gitblit/wicket/pages/EditRepositoryPage.java index 505cb54..214d0f0 100644 --- a/src/com/gitblit/wicket/pages/EditRepositoryPage.java +++ b/src/com/gitblit/wicket/pages/EditRepositoryPage.java @@ -343,6 +343,7 @@ form.add(new TextField<String>("description")); form.add(new DropDownChoice<String>("owner", GitBlit.self().getAllUsernames()) .setEnabled(GitBlitWebSession.get().canAdmin())); + form.add(new CheckBox("allowForks")); form.add(new DropDownChoice<AccessRestrictionType>("accessRestriction", Arrays .asList(AccessRestrictionType.values()), new AccessRestrictionRenderer())); form.add(new CheckBox("isFrozen")); diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/com/gitblit/wicket/pages/EditUserPage.html index 1c076bd..685eb64 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.html +++ b/src/com/gitblit/wicket/pages/EditUserPage.html @@ -17,12 +17,13 @@ <tr><th><wicket:message key="gb.displayName"></wicket:message></th><td class="edit"><input type="text" wicket:id="displayName" size="30" tabindex="4" /></td></tr> <tr><th><wicket:message key="gb.emailAddress"></wicket:message></th><td class="edit"><input type="text" wicket:id="emailAddress" size="30" tabindex="5" /></td></tr> <tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> <span class="help-inline"><wicket:message key="gb.canAdminDescription"></wicket:message></span></label></td></tr> - <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="7" /> <span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr> + <tr><th><wicket:message key="gb.canFork"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="canFork" tabindex="7" /> <span class="help-inline"><wicket:message key="gb.canForkDescription"></wicket:message></span></label></td></tr> + <tr><th><wicket:message key="gb.excludeFromFederation"></wicket:message></th><td class="edit"><label class="checkbox"><input type="checkbox" wicket:id="excludeFromFederation" tabindex="8" /> <span class="help-inline"><wicket:message key="gb.excludeFromFederationDescription"></wicket:message></span></label></td></tr> <tr><td colspan="2" style="padding-top:15px;"><h3><wicket:message key="gb.accessPermissions"></wicket:message> <small><wicket:message key="gb.accessPermissionsForUserDescription"></wicket:message></small></h3></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.teamMemberships"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr> <tr><td colspan="2"><hr></hr></td></tr> <tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr> - <tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="8" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="9" /></div></td></tr> + <tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="9" /> <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="10" /></div></td></tr> </tbody> </table> </form> diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java index cfe7c35..31f91c1 100644 --- a/src/com/gitblit/wicket/pages/EditUserPage.java +++ b/src/com/gitblit/wicket/pages/EditUserPage.java @@ -231,6 +231,7 @@ form.add(new TextField<String>("displayName").setEnabled(editDisplayName)); form.add(new TextField<String>("emailAddress").setEnabled(editEmailAddress)); form.add(new CheckBox("canAdmin")); + form.add(new CheckBox("canFork")); form.add(new CheckBox("excludeFromFederation")); form.add(repositories); form.add(teams.setEnabled(editTeams)); diff --git a/src/com/gitblit/wicket/pages/ForksPage.html b/src/com/gitblit/wicket/pages/ForksPage.html new file mode 100644 index 0000000..68f8489 --- /dev/null +++ b/src/com/gitblit/wicket/pages/ForksPage.html @@ -0,0 +1,24 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" + xml:lang="en" + lang="en"> + +<body> +<wicket:extend> + + <div class="forkSource"> + <b><span class="repositorySwatch" wicket:id="forkSourceSwatch"></span></b> + <span wicket:id="forkSourceAvatar" style="vertical-align: baseline;"></span> + <span wicket:id="forkSourceProject">[a project]</span> / <span wicket:id="forkSource">[a fork]</span> + </div> + + <div wicket:id="fork"> + <div class="forkEntry"> + <span wicket:id="anAvatar" style="vertical-align: baseline;"></span> + <span wicket:id="aProject">[a project]</span> / <span wicket:id="aFork">[a fork]</span> + </div> + </div> +</wicket:extend> +</body> +</html> \ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/ForksPage.java b/src/com/gitblit/wicket/pages/ForksPage.java new file mode 100644 index 0000000..7b8235b --- /dev/null +++ b/src/com/gitblit/wicket/pages/ForksPage.java @@ -0,0 +1,133 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.pages; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.wicket.Component; +import org.apache.wicket.PageParameters; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.DataView; +import org.apache.wicket.markup.repeater.data.ListDataProvider; +import org.eclipse.jgit.lib.PersonIdent; + +import com.gitblit.GitBlit; +import com.gitblit.Keys; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.panels.GravatarImage; +import com.gitblit.wicket.panels.LinkPanel; + +public class ForksPage extends RepositoryPage { + + public ForksPage(PageParameters params) { + super(params); + + RepositoryModel model = getRepositoryModel(); + RepositoryModel origin; + List<String> list; + if (ArrayUtils.isEmpty(model.forks)) { + // origin repository has forks + origin = GitBlit.self().getRepositoryModel(model.originRepository); + list = new ArrayList<String>(origin.forks); + } else { + // this repository has forks + origin = model; + list = new ArrayList<String>(model.forks); + } + + if (origin.isPersonalRepository()) { + // personal repository + UserModel user = GitBlit.self().getUserModel(origin.projectPath.substring(1)); + PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress); + add(new GravatarImage("forkSourceAvatar", ident, 20)); + add(new Label("forkSourceSwatch").setVisible(false)); + add(new LinkPanel("forkSourceProject", null, user.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(user.username))); + } else { + // standard repository + add(new GravatarImage("forkSourceAvatar", new PersonIdent("", ""), 20).setVisible(false)); + Component swatch; + if (origin.isBare){ + swatch = new Label("forkSourceSwatch", " ").setEscapeModelStrings(false); + } else { + swatch = new Label("forkSourceSwatch", "!"); + WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning")); + } + WicketUtils.setCssBackground(swatch, origin.toString()); + add(swatch); + final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true); + swatch.setVisible(showSwatch); + + String projectName = origin.projectPath; + if (StringUtils.isEmpty(projectName)) { + projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main"); + } + add(new LinkPanel("forkSourceProject", null, projectName, ProjectPage.class, WicketUtils.newProjectParameter(origin.projectPath))); + } + + String source = StringUtils.getLastPathElement(origin.name); + add(new LinkPanel("forkSource", null, StringUtils.stripDotGit(source), SummaryPage.class, WicketUtils.newRepositoryParameter(origin.name))); + + // only display user-accessible forks + UserModel user = GitBlitWebSession.get().getUser(); + List<RepositoryModel> forks = new ArrayList<RepositoryModel>(); + for (String aFork : list) { + RepositoryModel fork = GitBlit.self().getRepositoryModel(user, aFork); + if (fork != null) { + forks.add(fork); + } + } + + ListDataProvider<RepositoryModel> forksDp = new ListDataProvider<RepositoryModel>(forks); + DataView<RepositoryModel> forksList = new DataView<RepositoryModel>("fork", forksDp) { + private static final long serialVersionUID = 1L; + + public void populateItem(final Item<RepositoryModel> item) { + RepositoryModel fork = item.getModelObject(); + + if (fork.isPersonalRepository()) { + UserModel user = GitBlit.self().getUserModel(fork.projectPath.substring(1)); + PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress); + item.add(new GravatarImage("anAvatar", ident, 20)); + item.add(new LinkPanel("aProject", null, user.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(user.username))); + } else { + PersonIdent ident = new PersonIdent(fork.name, fork.name); + item.add(new GravatarImage("anAvatar", ident, 20)); + item.add(new LinkPanel("aProject", null, fork.projectPath, ProjectPage.class, WicketUtils.newProjectParameter(fork.projectPath))); + } + + String repo = StringUtils.getLastPathElement(fork.name); + item.add(new LinkPanel("aFork", null, StringUtils.stripDotGit(repo), SummaryPage.class, WicketUtils.newRepositoryParameter(fork.name))); + + WicketUtils.setCssStyle(item, "margin-left:25px;"); + } + }; + + add(forksList); + + } + + @Override + protected String getPageName() { + return getString("gb.forks"); + } +} diff --git a/src/com/gitblit/wicket/pages/GitSearchPage.java b/src/com/gitblit/wicket/pages/GitSearchPage.java index ca813ac..6b0714f 100644 --- a/src/com/gitblit/wicket/pages/GitSearchPage.java +++ b/src/com/gitblit/wicket/pages/GitSearchPage.java @@ -36,7 +36,7 @@ int nextPage = pageNumber + 1; SearchPanel search = new SearchPanel("searchPanel", repositoryName, objectId, value, - searchType, getRepository(), -1, pageNumber - 1); + searchType, getRepository(), -1, pageNumber - 1, getRepositoryModel().showRemoteBranches); boolean hasMore = search.hasMore(); add(search); diff --git a/src/com/gitblit/wicket/pages/HistoryPage.java b/src/com/gitblit/wicket/pages/HistoryPage.java index 122eeb8..563202e 100644 --- a/src/com/gitblit/wicket/pages/HistoryPage.java +++ b/src/com/gitblit/wicket/pages/HistoryPage.java @@ -32,7 +32,7 @@ int nextPage = pageNumber + 1; HistoryPanel history = new HistoryPanel("historyPanel", repositoryName, objectId, path, - getRepository(), -1, pageNumber - 1); + getRepository(), -1, pageNumber - 1, getRepositoryModel().showRemoteBranches); boolean hasMore = history.hasMore(); add(history); diff --git a/src/com/gitblit/wicket/pages/LogPage.java b/src/com/gitblit/wicket/pages/LogPage.java index d3dc3a9..ee8ddfe 100644 --- a/src/com/gitblit/wicket/pages/LogPage.java +++ b/src/com/gitblit/wicket/pages/LogPage.java @@ -37,7 +37,7 @@ refid = getRepositoryModel().HEAD; } LogPanel logPanel = new LogPanel("logPanel", repositoryName, refid, getRepository(), -1, - pageNumber - 1); + pageNumber - 1, getRepositoryModel().showRemoteBranches); boolean hasMore = logPanel.hasMore(); add(logPanel); diff --git a/src/com/gitblit/wicket/pages/ProjectPage.html b/src/com/gitblit/wicket/pages/ProjectPage.html index db10329..3e73ba5 100644 --- a/src/com/gitblit/wicket/pages/ProjectPage.html +++ b/src/com/gitblit/wicket/pages/ProjectPage.html @@ -7,29 +7,6 @@ <body> <wicket:extend> - <wicket:fragment wicket:id="repositoryAdminLinks"> - <span class="link"> - <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> - | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> - | <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"> - <span class="link"> - <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> - | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> - | <a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> - </span> - </wicket:fragment> - - <wicket:fragment wicket:id="repositoryUserLinks"> - <span class="link"> - <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> - | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> - </span> - </wicket:fragment> <div class="row"> <div class="span12"> @@ -60,50 +37,11 @@ <div class="markdown" wicket:id="repositoriesMessage">[repositories message]</div> </div> </div> - - <!-- repositories --> <div class="row"> - <div class="span6" style="border-top:1px solid #eee;" wicket:id="repository"> - <div style="padding-top:15px;padding-bottom:15px;margin-right:15px;"> - <div class="pull-right" style="text-align:right;padding-right:15px;"> - <span wicket:id="repositoryLinks"></span> - - <div> - <img class="inlineIcon" wicket:id="frozenIcon" /> - <img class="inlineIcon" wicket:id="federatedIcon" /> - - <a style="text-decoration: none;" wicket:id="tickets" wicket:message="title:gb.tickets"> - <img style="border:0px;vertical-align:middle;" src="bug_16x16.png"></img> - </a> - <a style="text-decoration: none;" wicket:id="docs" wicket:message="title:gb.docs"> - <img style="border:0px;vertical-align:middle;" src="book_16x16.png"></img> - </a> - <a style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed"> - <img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img> - </a> - </div> - <span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryOwner">[owner]</span> - </div> - - <h3><span class="repositorySwatch" wicket:id="repositorySwatch"></span> - <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span> - <img class="inlineIcon" wicket:id="accessRestrictionIcon" /> - </h3> - - <div style="padding-left:20px;"> - - <div style="padding-bottom:10px" wicket:id="repositoryDescription">[repository description]</div> - - <div style="color: #999;"> - <wicket:message key="gb.lastChange">[last change]</wicket:message> <span wicket:id="repositoryLastChange">[last change]</span>, - <span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span> - </div> - - <div class="hidden-phone hidden-tablet" wicket:id="repositoryCloneUrl">[repository clone url]</div> - </div> - </div> - </div> - </div> + <div class="span6" style="border-bottom:1px solid #eee;" wicket:id="repositoryList"> + <span wicket:id="repository"></span> + </div> + </div> </div> <!-- activity tab --> diff --git a/src/com/gitblit/wicket/pages/ProjectPage.java b/src/com/gitblit/wicket/pages/ProjectPage.java index 808cc06..3679a6f 100644 --- a/src/com/gitblit/wicket/pages/ProjectPage.java +++ b/src/com/gitblit/wicket/pages/ProjectPage.java @@ -34,10 +34,7 @@ import org.apache.wicket.RedirectException; import org.apache.wicket.behavior.HeaderContributor; import org.apache.wicket.markup.html.basic.Label; -import org.apache.wicket.markup.html.link.BookmarkablePageLink; import org.apache.wicket.markup.html.link.ExternalLink; -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; import org.apache.wicket.markup.repeater.data.ListDataProvider; @@ -52,7 +49,6 @@ import com.gitblit.models.RepositoryModel; import com.gitblit.models.UserModel; import com.gitblit.utils.ActivityUtils; -import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitBlitWebApp; @@ -66,9 +62,7 @@ import com.gitblit.wicket.charting.GoogleLineChart; import com.gitblit.wicket.charting.GooglePieChart; import com.gitblit.wicket.panels.ActivityPanel; -import com.gitblit.wicket.panels.BasePanel.JavascriptEventConfirmation; -import com.gitblit.wicket.panels.LinkPanel; -import com.gitblit.wicket.panels.RepositoryUrlPanel; +import com.gitblit.wicket.panels.ProjectRepositoryPanel; public class ProjectPage extends RootPage { @@ -148,143 +142,16 @@ } }); - final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true); - final boolean gitServlet = GitBlit.getBoolean(Keys.git.enableGitServlet, true); - final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true); - final ListDataProvider<RepositoryModel> dp = new ListDataProvider<RepositoryModel>(repositories); - DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repository", dp) { + DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repositoryList", dp) { private static final long serialVersionUID = 1L; public void populateItem(final Item<RepositoryModel> item) { final RepositoryModel entry = item.getModelObject(); - - // repository swatch - Component swatch; - if (entry.isBare){ - swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false); - } else { - swatch = new Label("repositorySwatch", "!"); - WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning")); - } - WicketUtils.setCssBackground(swatch, entry.toString()); - item.add(swatch); - swatch.setVisible(showSwatch); - PageParameters pp = WicketUtils.newRepositoryParameter(entry.name); - item.add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(projectPath, StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp)); - item.add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils.isEmpty(entry.description))); - - item.add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets)); - item.add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs)); - - if (entry.isFrozen) { - item.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", - getString("gb.isFrozen"))); - } else { - item.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false)); - } - - if (entry.isFederated) { - item.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", - getString("gb.isFederated"))); - } else { - item.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false)); - } - switch (entry.accessRestriction) { - case NONE: - item.add(WicketUtils.newBlankImage("accessRestrictionIcon").setVisible(false)); - break; - case PUSH: - item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", - getAccessRestrictions().get(entry.accessRestriction))); - break; - case CLONE: - item.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", - getAccessRestrictions().get(entry.accessRestriction))); - break; - case VIEW: - item.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", - getAccessRestrictions().get(entry.accessRestriction))); - break; - default: - item.add(WicketUtils.newBlankImage("accessRestrictionIcon")); - } - - item.add(new Label("repositoryOwner", StringUtils.isEmpty(entry.owner) ? "" : (entry.owner + " (" + getString("gb.owner") + ")"))); - - - UserModel user = GitBlitWebSession.get().getUser(); - Fragment repositoryLinks; - boolean showOwner = user != null && user.username.equalsIgnoreCase(entry.owner); - if (showAdmin || showOwner) { - repositoryLinks = new Fragment("repositoryLinks", - showAdmin ? "repositoryAdminLinks" : "repositoryOwnerLinks", this); - repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", - EditRepositoryPage.class, WicketUtils - .newRepositoryParameter(entry.name))); - if (showAdmin) { - 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(getString("gb.repositoryDeleted"), entry)); - // TODO dp.remove(entry); - } else { - error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry)); - } - } - }; - deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format( - getString("gb.deleteRepository"), entry))); - repositoryLinks.add(deleteLink); - } - } else { - repositoryLinks = new Fragment("repositoryLinks", "repositoryUserLinks", this); - } - - repositoryLinks.add(new BookmarkablePageLink<Void>("tree", TreePage.class, - WicketUtils.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits)); - - repositoryLinks.add(new BookmarkablePageLink<Void>("log", LogPage.class, - WicketUtils.newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits)); - - item.add(repositoryLinks); - - String lastChange; - if (entry.lastChange.getTime() == 0) { - lastChange = "--"; - } else { - lastChange = getTimeUtils().timeAgo(entry.lastChange); - } - Label lastChangeLabel = new Label("repositoryLastChange", lastChange); - item.add(lastChangeLabel); - WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange)); - - if (entry.hasCommits) { - // Existing repository - item.add(new Label("repositorySize", entry.size).setVisible(showSize)); - } else { - // New repository - item.add(new Label("repositorySize", getString("gb.empty")) - .setEscapeModelStrings(false)); - } - - item.add(new ExternalLink("syndication", SyndicationServlet.asLink("", - entry.name, null, 0))); - - List<String> repositoryUrls = new ArrayList<String>(); - if (gitServlet) { - // add the Gitblit repository url - repositoryUrls.add(getRepositoryUrl(entry)); - } - repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(entry.name)); - - String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0); - item.add(new RepositoryUrlPanel("repositoryCloneUrl", primaryUrl)); + ProjectRepositoryPanel row = new ProjectRepositoryPanel("repository", + getLocalizer(), this, showAdmin, entry, getAccessRestrictions()); + item.add(row); } }; add(dataView); @@ -434,7 +301,7 @@ protected List<ProjectModel> getProjectModels() { if (projectModels.isEmpty()) { final UserModel user = GitBlitWebSession.get().getUser(); - List<ProjectModel> projects = GitBlit.self().getProjectModels(user); + List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false); projectModels.addAll(projects); } return projectModels; @@ -451,7 +318,12 @@ protected List<DropDownMenuItem> getProjectsMenu() { List<DropDownMenuItem> menu = new ArrayList<DropDownMenuItem>(); - List<ProjectModel> projects = getProjectModels(); + List<ProjectModel> projects = new ArrayList<ProjectModel>(); + for (ProjectModel model : getProjectModels()) { + if (!model.isUserProject()) { + projects.add(model); + } + } int maxProjects = 15; boolean showAllProjects = projects.size() > maxProjects; if (showAllProjects) { diff --git a/src/com/gitblit/wicket/pages/ProjectsPage.java b/src/com/gitblit/wicket/pages/ProjectsPage.java index f3c4416..7161d0f 100644 --- a/src/com/gitblit/wicket/pages/ProjectsPage.java +++ b/src/com/gitblit/wicket/pages/ProjectsPage.java @@ -36,6 +36,7 @@ import com.gitblit.GitBlit; import com.gitblit.Keys; import com.gitblit.models.ProjectModel; +import com.gitblit.models.UserModel; import com.gitblit.utils.MarkdownUtils; import com.gitblit.utils.StringUtils; import com.gitblit.wicket.GitBlitWebSession; @@ -63,6 +64,13 @@ protected boolean reusePageParameters() { return true; } + + @Override + protected List<ProjectModel> getProjectModels() { + final UserModel user = GitBlitWebSession.get().getUser(); + List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false); + return projects; + } private void setup(PageParameters params) { setupPage("", ""); diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.html b/src/com/gitblit/wicket/pages/RepositoryPage.html index de64fce..4b53dba 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.html +++ b/src/com/gitblit/wicket/pages/RepositoryPage.html @@ -42,9 +42,18 @@ <!-- page header --> <div class="pageTitle"> <div class="row"> - <div wicket:id="workingCopy"></div> - <div class="span9"> - <h2><span wicket:id="repositoryName">[repository name]</span> <small><span wicket:id="pageName">[page name]</span></small></h2> + <div class="controls"> + <span wicket:id="workingCopyIndicator"></span> + <span wicket:id="forksProhibitedIndicator"></span> + <div class="hidden-phone btn-group pull-right"> + <!-- future spot for other repo buttons --> + <a class="btn" wicket:id="myForkLink"><i class="icon-random"></i> <wicket:message key="gb.myFork"></wicket:message></a> + <a class="btn" wicket:id="forkLink"><i class="icon-random"></i> <wicket:message key="gb.fork"></wicket:message></a> + </div> + </div> + <div class="span7"> + <div><span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span> <span wicket:id="pageName">[page name]</span></div> + <span wicket:id="originRepository">[origin repository]</span> </div> </div> </div> @@ -52,11 +61,22 @@ <wicket:child /> </div> - <wicket:fragment wicket:id="workingCopyFragment"> - <p class="pull-right" style="padding-top:5px;"> - <span class="alert alert-info" style="padding: 8px 14px 8px 14px;vertical-align: middle;"><i class="icon-exclamation-sign" style="vertical-align: middle;"></i> <span class="hidden-phone" wicket:id="workingCopy" style="font-weight:bold;">[working copy]</span></span> - </p> + <wicket:fragment wicket:id="originFragment"> + <p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p> </wicket:fragment> + + <wicket:fragment wicket:id="workingCopyFragment"> + <div class="pull-right" style="padding-top:0px;margin-bottom:0px;padding-left:5px"> + <span class="alert alert-info" style="padding: 6px 14px 6px 14px;vertical-align: middle;"><i class="icon-exclamation-sign"></i> <span class="hidden-phone" wicket:id="workingCopy" style="font-weight:bold;">[working copy]</span></span> + </div> + </wicket:fragment> + + <wicket:fragment wicket:id="forksProhibitedFragment"> + <div class="pull-right" style="padding-top:0px;margin-bottom:0px;padding-left:5px"> + <span class="alert alert-error" style="padding: 6px 14px 6px 14px;vertical-align: middle;"><i class="icon-ban-circle"></i> <span class="hidden-phone" wicket:id="forksProhibited" style="font-weight:bold;">[forks prohibited]</span></span> + </div> + </wicket:fragment> + </wicket:extend> </body> </html> \ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java index eb8536c..a85d21e 100644 --- a/src/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/com/gitblit/wicket/pages/RepositoryPage.java @@ -28,10 +28,12 @@ import org.apache.wicket.Component; import org.apache.wicket.PageParameters; +import org.apache.wicket.RedirectException; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.TextField; import org.apache.wicket.markup.html.link.ExternalLink; +import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.markup.html.panel.Fragment; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; @@ -47,8 +49,10 @@ import com.gitblit.Keys; import com.gitblit.PagesServlet; import com.gitblit.SyndicationServlet; +import com.gitblit.models.ProjectModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.SubmoduleModel; +import com.gitblit.models.UserModel; import com.gitblit.utils.ArrayUtils; import com.gitblit.utils.JGitUtils; import com.gitblit.utils.StringUtils; @@ -58,6 +62,7 @@ import com.gitblit.wicket.PageRegistration.OtherPageLink; import com.gitblit.wicket.SessionlessForm; import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.panels.BasePanel.JavascriptEventConfirmation; import com.gitblit.wicket.panels.LinkPanel; import com.gitblit.wicket.panels.NavigationPanel; import com.gitblit.wicket.panels.RefsPanel; @@ -81,10 +86,11 @@ public RepositoryPage(PageParameters params) { super(params); repositoryName = WicketUtils.getRepositoryName(params); - if (repositoryName.indexOf('/') > -1) { - projectName = repositoryName.substring(0, repositoryName.indexOf('/')); - } else { + String root =StringUtils.getFirstPathElement(repositoryName); + if (StringUtils.isEmpty(root)) { projectName = GitBlit.getString(Keys.web.repositoryRootGroupName, "main"); + } else { + projectName = root; } objectId = WicketUtils.getObject(params); @@ -125,7 +131,6 @@ // standard links pages.put("repositories", new PageRegistration("gb.repositories", RepositoriesPage.class)); - pages.put("project", new PageRegistration("gb.project", ProjectPage.class, WicketUtils.newProjectParameter(projectName))); pages.put("summary", new PageRegistration("gb.summary", SummaryPage.class, params)); pages.put("log", new PageRegistration("gb.log", LogPage.class, params)); pages.put("branches", new PageRegistration("gb.branches", BranchesPage.class, params)); @@ -136,6 +141,17 @@ Repository r = getRepository(); RepositoryModel model = getRepositoryModel(); + // forks list button + if (StringUtils.isEmpty(model.originRepository)) { + if (!ArrayUtils.isEmpty(model.forks)) { + // this origin repository has forks + pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params)); + } + } else { + // this is a fork of another repository + pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params)); + } + // per-repository extra page links if (model.useTickets && TicgitUtils.getTicketsBranch(r) != null) { pages.put("tickets", new PageRegistration("gb.tickets", TicketsPage.class, params)); @@ -168,19 +184,100 @@ @Override protected void setupPage(String repositoryName, String pageName) { - add(new LinkPanel("repositoryName", null, StringUtils.stripDotGit(repositoryName), - SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryName))); - add(new Label("pageName", pageName).setRenderBodyOnly(true)); - if (getRepositoryModel().isBare) { - add(new Label("workingCopy").setVisible(false)); + String projectName = StringUtils.getFirstPathElement(repositoryName); + ProjectModel project = GitBlit.self().getProjectModel(projectName); + if (project.isUserProject()) { + // user-as-project + add(new LinkPanel("projectTitle", null, project.getDisplayName(), + UserPage.class, WicketUtils.newUsernameParameter(project.name.substring(1)))); } else { - Fragment fragment = new Fragment("workingCopy", "workingCopyFragment", this); + // project + add(new LinkPanel("projectTitle", null, project.name, + ProjectPage.class, WicketUtils.newProjectParameter(project.name))); + } + + String name = StringUtils.stripDotGit(repositoryName); + if (!StringUtils.isEmpty(projectName) && name.startsWith(projectName)) { + name = name.substring(projectName.length() + 1); + } + add(new LinkPanel("repositoryName", null, name, SummaryPage.class, + WicketUtils.newRepositoryParameter(repositoryName))); + add(new Label("pageName", pageName).setRenderBodyOnly(true)); + + // indicate origin repository + RepositoryModel model = getRepositoryModel(); + if (StringUtils.isEmpty(model.originRepository)) { + add(new Label("originRepository").setVisible(false)); + } else { + Fragment forkFrag = new Fragment("originRepository", "originFragment", this); + forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(model.originRepository), + SummaryPage.class, WicketUtils.newRepositoryParameter(model.originRepository))); + add(forkFrag); + } + + if (getRepositoryModel().isBare) { + add(new Label("workingCopyIndicator").setVisible(false)); + } else { + Fragment wc = new Fragment("workingCopyIndicator", "workingCopyFragment", this); Label lbl = new Label("workingCopy", getString("gb.workingCopy")); WicketUtils.setHtmlTooltip(lbl, getString("gb.workingCopyWarning")); - fragment.add(lbl); - add(fragment); + wc.add(lbl); + add(wc); } + + if (getRepositoryModel().allowForks) { + add(new Label("forksProhibitedIndicator").setVisible(false)); + } else { + Fragment wc = new Fragment("forksProhibitedIndicator", "forksProhibitedFragment", this); + Label lbl = new Label("forksProhibited", getString("gb.forksProhibited")); + WicketUtils.setHtmlTooltip(lbl, getString("gb.forksProhibitedWarning")); + wc.add(lbl); + add(wc); + } + + UserModel user = GitBlitWebSession.get().getUser(); + + // fork button + if (user != null) { + final String clonedRepo = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(model.name))); + boolean hasClone = GitBlit.self().hasRepository(clonedRepo) && !getRepositoryModel().name.equals(clonedRepo); + if (user.canForkRepository(model) && !hasClone) { + Link<Void> forkLink = new Link<Void>("forkLink") { + private static final long serialVersionUID = 1L; + + @Override + public void onClick() { + RepositoryModel model = getRepositoryModel(); + if (GitBlit.self().fork(model, GitBlitWebSession.get().getUser())) { + throw new RedirectException(SummaryPage.class, WicketUtils.newRepositoryParameter(clonedRepo)); + } else { + error(MessageFormat.format(getString("gb.repositoryForkFailed"), model)); + } + } + }; + forkLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format( + getString("gb.forkRepository"), getRepositoryModel()))); + add(forkLink); + } else { + // user not allowed to fork or fork already exists or repo forbids forking + add(new ExternalLink("forkLink", "").setVisible(false)); + } + + if (hasClone) { + // user has clone + String url = getRequestCycle().urlFor(SummaryPage.class, WicketUtils.newRepositoryParameter(clonedRepo)).toString(); + add(new ExternalLink("myForkLink", url)); + } else { + // user does not have clone + add(new ExternalLink("myForkLink", "").setVisible(false)); + } + } else { + // server prohibits forking + add(new ExternalLink("forkLink", "").setVisible(false)); + add(new ExternalLink("myForkLink", "").setVisible(false)); + } + super.setupPage(repositoryName, pageName); } @@ -312,7 +409,7 @@ } protected void addRefs(Repository r, RevCommit c) { - add(new RefsPanel("refsPanel", repositoryName, c, JGitUtils.getAllRefs(r))); + add(new RefsPanel("refsPanel", repositoryName, c, JGitUtils.getAllRefs(r, getRepositoryModel().showRemoteBranches))); } protected void addFullText(String wicketId, String text, boolean substituteRegex) { diff --git a/src/com/gitblit/wicket/pages/RootPage.java b/src/com/gitblit/wicket/pages/RootPage.java index 4858368..1e6f130 100644 --- a/src/com/gitblit/wicket/pages/RootPage.java +++ b/src/com/gitblit/wicket/pages/RootPage.java @@ -184,6 +184,9 @@ // remove named repository parameter params.remove("r"); + // remove named user parameter + params.remove("user"); + // remove days back parameter if it is the default value if (params.containsKey("db") && params.getInt("db") == GitBlit.getInteger(Keys.web.activityDuration, 14)) { @@ -327,6 +330,12 @@ boolean hasParameter = false; String projectName = WicketUtils.getProjectName(params); + String userName = WicketUtils.getUsername(params); + if (StringUtils.isEmpty(projectName)) { + if (!StringUtils.isEmpty(userName)) { + projectName = "~" + userName; + } + } String repositoryName = WicketUtils.getRepositoryName(params); String set = WicketUtils.getSet(params); String regex = WicketUtils.getRegEx(params); diff --git a/src/com/gitblit/wicket/pages/SummaryPage.java b/src/com/gitblit/wicket/pages/SummaryPage.java index 39c7269..22b1c1a 100644 --- a/src/com/gitblit/wicket/pages/SummaryPage.java +++ b/src/com/gitblit/wicket/pages/SummaryPage.java @@ -130,7 +130,7 @@ add(new Label("otherUrls", StringUtils.flattenStrings(repositoryUrls, "<br/>")) .setEscapeModelStrings(false)); - add(new LogPanel("commitsPanel", repositoryName, getRepositoryModel().HEAD, r, numberCommits, 0)); + add(new LogPanel("commitsPanel", repositoryName, getRepositoryModel().HEAD, r, numberCommits, 0, getRepositoryModel().showRemoteBranches)); add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty()); add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty()); diff --git a/src/com/gitblit/wicket/pages/UserPage.html b/src/com/gitblit/wicket/pages/UserPage.html new file mode 100644 index 0000000..5886a3a --- /dev/null +++ b/src/com/gitblit/wicket/pages/UserPage.html @@ -0,0 +1,44 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" + xml:lang="en" + lang="en"> + +<body> +<wicket:extend> + + <div class="row"> + <div class="span4"> + <div wicket:id="gravatar"></div> + <div style="text-align: left;"> + <h2><span wicket:id="userDisplayName"></span></h2> + <div><i class="icon-user"></i> <span wicket:id="userUsername"></span></div> + <div><i class="icon-envelope"></i><span wicket:id="userEmail"></span></div> + </div> + </div> + + <div class="span8"> + <div class="tabbable"> + <!-- tab titles --> + <ul class="nav nav-tabs"> + <li class="active"><a href="#repositories" data-toggle="tab"><wicket:message key="gb.repositories"></wicket:message></a></li> + </ul> + + <!-- tab content --> + <div class="tab-content"> + + <!-- repositories tab --> + <div class="tab-pane active" id="repositories"> + <table width="100%"> + <tbody> + <tr wicket:id="repositoryList"><td style="border-bottom:1px solid #eee;"><span wicket:id="repository"></span></td></tr> + </tbody> + </table> + </div> + </div> + </div> + </div> + </div> +</wicket:extend> +</body> +</html> \ No newline at end of file diff --git a/src/com/gitblit/wicket/pages/UserPage.java b/src/com/gitblit/wicket/pages/UserPage.java new file mode 100644 index 0000000..cabefb4 --- /dev/null +++ b/src/com/gitblit/wicket/pages/UserPage.java @@ -0,0 +1,146 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.pages; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import org.apache.wicket.PageParameters; +import org.apache.wicket.RedirectException; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.repeater.Item; +import org.apache.wicket.markup.repeater.data.DataView; +import org.apache.wicket.markup.repeater.data.ListDataProvider; +import org.eclipse.jgit.lib.PersonIdent; + +import com.gitblit.GitBlit; +import com.gitblit.Keys; +import com.gitblit.models.ProjectModel; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitBlitWebApp; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.PageRegistration; +import com.gitblit.wicket.PageRegistration.DropDownMenuItem; +import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.panels.GravatarImage; +import com.gitblit.wicket.panels.LinkPanel; +import com.gitblit.wicket.panels.ProjectRepositoryPanel; + +public class UserPage extends RootPage { + + List<ProjectModel> projectModels = new ArrayList<ProjectModel>(); + + public UserPage() { + super(); + throw new RedirectException(GitBlitWebApp.get().getHomePage()); + } + + public UserPage(PageParameters params) { + super(params); + setup(params); + } + + @Override + protected boolean reusePageParameters() { + return true; + } + + private void setup(PageParameters params) { + setupPage("", ""); + // check to see if we should display a login message + boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true); + if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) { + authenticationError("Please login"); + return; + } + + String userName = WicketUtils.getUsername(params); + if (StringUtils.isEmpty(userName)) { + throw new RedirectException(GitBlitWebApp.get().getHomePage()); + } + + UserModel user = GitBlit.self().getUserModel(userName); + if (user == null) { + // construct a temporary user model + user = new UserModel(userName); + } + + String projectName = "~" + userName; + + ProjectModel project = GitBlit.self().getProjectModel(projectName); + if (project == null) { + throw new RedirectException(GitBlitWebApp.get().getHomePage()); + } + + add(new Label("userDisplayName", user.getDisplayName())); + add(new Label("userUsername", user.username)); + LinkPanel email = new LinkPanel("userEmail", null, user.emailAddress, "mailto:#"); + email.setRenderBodyOnly(true); + add(email.setVisible(GitBlit.getBoolean(Keys.web.showEmailAddresses, true) && !StringUtils.isEmpty(user.emailAddress))); + + PersonIdent person = new PersonIdent(user.getDisplayName(), user.emailAddress); + add(new GravatarImage("gravatar", person, 210)); + + List<RepositoryModel> repositories = getRepositories(params); + + Collections.sort(repositories, new Comparator<RepositoryModel>() { + @Override + public int compare(RepositoryModel o1, RepositoryModel o2) { + // reverse-chronological sort + return o2.lastChange.compareTo(o1.lastChange); + } + }); + + final ListDataProvider<RepositoryModel> dp = new ListDataProvider<RepositoryModel>(repositories); + DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repositoryList", dp) { + private static final long serialVersionUID = 1L; + + public void populateItem(final Item<RepositoryModel> item) { + final RepositoryModel entry = item.getModelObject(); + + ProjectRepositoryPanel row = new ProjectRepositoryPanel("repository", + getLocalizer(), this, showAdmin, entry, getAccessRestrictions()); + item.add(row); + } + }; + add(dataView); + } + + @Override + protected void addDropDownMenus(List<PageRegistration> pages) { + PageParameters params = getPageParameters(); + + DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters", + UserPage.class); + // preserve time filter option on repository choices + menu.menuItems.addAll(getRepositoryFilterItems(params)); + + // preserve repository filter option on time choices + menu.menuItems.addAll(getTimeFilterItems(params)); + + if (menu.menuItems.size() > 0) { + // Reset Filter + menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null)); + } + + pages.add(menu); + } +} diff --git a/src/com/gitblit/wicket/panels/GravatarImage.java b/src/com/gitblit/wicket/panels/GravatarImage.java index b1c7b65..f26a902 100644 --- a/src/com/gitblit/wicket/panels/GravatarImage.java +++ b/src/com/gitblit/wicket/panels/GravatarImage.java @@ -48,7 +48,7 @@ public GravatarImage(String id, PersonIdent person, int width) { super(id); - String email = person.getEmailAddress().toLowerCase(); + String email = person.getEmailAddress() == null ? person.getName().toLowerCase() : person.getEmailAddress().toLowerCase(); String hash = StringUtils.getMD5(email); Link<Void> link = new BookmarkablePageLink<Void>("link", GravatarProfilePage.class, WicketUtils.newObjectParameter(hash)); diff --git a/src/com/gitblit/wicket/panels/HistoryPanel.java b/src/com/gitblit/wicket/panels/HistoryPanel.java index befd701..14aed91 100644 --- a/src/com/gitblit/wicket/panels/HistoryPanel.java +++ b/src/com/gitblit/wicket/panels/HistoryPanel.java @@ -54,7 +54,7 @@ private boolean hasMore; public HistoryPanel(String wicketId, final String repositoryName, final String objectId, - final String path, Repository r, int limit, int pageOffset) { + final String path, Repository r, int limit, int pageOffset, boolean showRemoteRefs) { super(wicketId); boolean pageResults = limit <= 0; int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50); @@ -74,7 +74,7 @@ } final boolean isTree = matchingPath == null ? true : matchingPath.isTree(); - final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r); + final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs); List<RevCommit> commits; if (pageResults) { // Paging result set diff --git a/src/com/gitblit/wicket/panels/LogPanel.java b/src/com/gitblit/wicket/panels/LogPanel.java index f441ba5..a31c3df 100644 --- a/src/com/gitblit/wicket/panels/LogPanel.java +++ b/src/com/gitblit/wicket/panels/LogPanel.java @@ -49,7 +49,7 @@ private boolean hasMore; public LogPanel(String wicketId, final String repositoryName, final String objectId, - Repository r, int limit, int pageOffset) { + Repository r, int limit, int pageOffset, boolean showRemoteRefs) { super(wicketId); boolean pageResults = limit <= 0; int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50); @@ -57,7 +57,7 @@ itemsPerPage = 50; } - final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r); + final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs); List<RevCommit> commits; if (pageResults) { // Paging result set diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html new file mode 100644 index 0000000..4678153 --- /dev/null +++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html @@ -0,0 +1,79 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd" + xml:lang="en" + lang="en"> + +<wicket:panel> + <wicket:fragment wicket:id="repositoryAdminLinks"> + <span class="link"> + <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> + | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> + | <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"> + <span class="link"> + <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> + | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> + | <a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> + </span> + </wicket:fragment> + + <wicket:fragment wicket:id="repositoryUserLinks"> + <span class="link"> + <a wicket:id="tree"><wicket:message key="gb.tree"></wicket:message></a> + | <a wicket:id="log"><wicket:message key="gb.log"></wicket:message></a> + </span> + </wicket:fragment> + + <wicket:fragment wicket:id="originFragment"> + <p class="originRepository" style="margin-left:20px;" ><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p> + </wicket:fragment> + + <div> + <div style="padding-top:15px;padding-bottom:15px;margin-right:15px;"> + <div class="pull-right" style="text-align:right;padding-right:15px;"> + <span wicket:id="repositoryLinks"></span> + <div> + <img class="inlineIcon" wicket:id="frozenIcon" /> + <img class="inlineIcon" wicket:id="federatedIcon" /> + + <a style="text-decoration: none;" wicket:id="tickets" wicket:message="title:gb.tickets"> + <img style="border:0px;vertical-align:middle;" src="bug_16x16.png"></img> + </a> + <a style="text-decoration: none;" wicket:id="docs" wicket:message="title:gb.docs"> + <img style="border:0px;vertical-align:middle;" src="book_16x16.png"></img> + </a> + <a style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed"> + <img style="border:0px;vertical-align:middle;" src="feed_16x16.png"></img> + </a> + </div> + <span style="color: #999;font-style:italic;font-size:0.8em;" wicket:id="repositoryOwner">[owner]</span> + </div> + + <div class="pageTitle" style="border:0px;"> + <div> + <span class="repositorySwatch" wicket:id="repositorySwatch"></span> + <span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span> + <img class="inlineIcon" style="vertical-align:baseline" wicket:id="accessRestrictionIcon" /> + </div> + <span wicket:id="originRepository">[origin repository]</span> + </div> + + <div style="padding-left:20px;"> + <div style="padding-bottom:10px" wicket:id="repositoryDescription">[repository description]</div> + + <div style="color: #999;"> + <wicket:message key="gb.lastChange">[last change]</wicket:message> <span wicket:id="repositoryLastChange">[last change]</span>, + <span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span> + </div> + + <div class="hidden-phone hidden-tablet" wicket:id="repositoryCloneUrl">[repository clone url]</div> + </div> + </div> + </div> +</wicket:panel> +</html> \ No newline at end of file diff --git a/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java new file mode 100644 index 0000000..f7deaf1 --- /dev/null +++ b/src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java @@ -0,0 +1,199 @@ +/* + * Copyright 2012 gitblit.com. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.gitblit.wicket.panels; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.wicket.Component; +import org.apache.wicket.Localizer; +import org.apache.wicket.PageParameters; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.link.BookmarkablePageLink; +import org.apache.wicket.markup.html.link.ExternalLink; +import org.apache.wicket.markup.html.link.Link; +import org.apache.wicket.markup.html.panel.Fragment; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.GitBlit; +import com.gitblit.Keys; +import com.gitblit.SyndicationServlet; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.ArrayUtils; +import com.gitblit.utils.StringUtils; +import com.gitblit.wicket.GitBlitWebSession; +import com.gitblit.wicket.WicketUtils; +import com.gitblit.wicket.pages.BasePage; +import com.gitblit.wicket.pages.DocsPage; +import com.gitblit.wicket.pages.EditRepositoryPage; +import com.gitblit.wicket.pages.LogPage; +import com.gitblit.wicket.pages.SummaryPage; +import com.gitblit.wicket.pages.TicketsPage; +import com.gitblit.wicket.pages.TreePage; + +public class ProjectRepositoryPanel extends BasePanel { + + private static final long serialVersionUID = 1L; + + public ProjectRepositoryPanel(String wicketId, Localizer localizer, Component owner, + final boolean isAdmin, final RepositoryModel entry, + final Map<AccessRestrictionType, String> accessRestrictions) { + super(wicketId); + + final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true); + final boolean gitServlet = GitBlit.getBoolean(Keys.git.enableGitServlet, true); + final boolean showSize = GitBlit.getBoolean(Keys.web.showRepositorySizes, true); + + // repository swatch + Component swatch; + if (entry.isBare) { + swatch = new Label("repositorySwatch", " ").setEscapeModelStrings(false); + } else { + swatch = new Label("repositorySwatch", "!"); + WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", owner)); + } + WicketUtils.setCssBackground(swatch, entry.toString()); + add(swatch); + swatch.setVisible(showSwatch); + + PageParameters pp = WicketUtils.newRepositoryParameter(entry.name); + add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath, + StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp)); + add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils + .isEmpty(entry.description))); + + if (StringUtils.isEmpty(entry.originRepository)) { + add(new Label("originRepository").setVisible(false)); + } else { + Fragment forkFrag = new Fragment("originRepository", "originFragment", this); + forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(entry.originRepository), + SummaryPage.class, WicketUtils.newRepositoryParameter(entry.originRepository))); + add(forkFrag); + } + + add(new BookmarkablePageLink<Void>("tickets", TicketsPage.class, pp).setVisible(entry.useTickets)); + add(new BookmarkablePageLink<Void>("docs", DocsPage.class, pp).setVisible(entry.useDocs)); + + if (entry.isFrozen) { + add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", owner))); + } else { + add(WicketUtils.newClearPixel("frozenIcon").setVisible(false)); + } + + if (entry.isFederated) { + add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", localizer.getString("gb.isFederated", owner))); + } else { + add(WicketUtils.newClearPixel("federatedIcon").setVisible(false)); + } + switch (entry.accessRestriction) { + case NONE: + add(WicketUtils.newBlankImage("accessRestrictionIcon").setVisible(false)); + break; + case PUSH: + add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", + accessRestrictions.get(entry.accessRestriction))); + break; + case CLONE: + add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", + accessRestrictions.get(entry.accessRestriction))); + break; + case VIEW: + add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", + accessRestrictions.get(entry.accessRestriction))); + break; + default: + add(WicketUtils.newBlankImage("accessRestrictionIcon")); + } + + add(new Label("repositoryOwner", StringUtils.isEmpty(entry.owner) ? "" : (entry.owner + " (" + + localizer.getString("gb.owner", owner) + ")"))); + + UserModel user = GitBlitWebSession.get().getUser(); + Fragment repositoryLinks; + boolean showOwner = user != null && user.username.equalsIgnoreCase(entry.owner); + // owner of personal repository gets admin powers + boolean showAdmin = isAdmin || entry.isUsersPersonalRepository(user.username); + + if (showAdmin || showOwner) { + repositoryLinks = new Fragment("repositoryLinks", showAdmin ? "repositoryAdminLinks" + : "repositoryOwnerLinks", this); + repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, + WicketUtils.newRepositoryParameter(entry.name))); + if (showAdmin) { + 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(getString("gb.repositoryDeleted"), entry)); + // TODO dp.remove(entry); + } else { + error(MessageFormat.format(getString("gb.repositoryDeleteFailed"), entry)); + } + } + }; + deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format( + localizer.getString("gb.deleteRepository", owner), entry))); + repositoryLinks.add(deleteLink); + } + } else { + repositoryLinks = new Fragment("repositoryLinks", "repositoryUserLinks", this); + } + + repositoryLinks.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils + .newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits)); + + repositoryLinks.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils + .newRepositoryParameter(entry.name)).setEnabled(entry.hasCommits)); + + add(repositoryLinks); + + String lastChange; + if (entry.lastChange.getTime() == 0) { + lastChange = "--"; + } else { + lastChange = getTimeUtils().timeAgo(entry.lastChange); + } + Label lastChangeLabel = new Label("repositoryLastChange", lastChange); + add(lastChangeLabel); + WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange)); + + if (entry.hasCommits) { + // Existing repository + add(new Label("repositorySize", entry.size).setVisible(showSize)); + } else { + // New repository + add(new Label("repositorySize", localizer.getString("gb.empty", owner)).setEscapeModelStrings(false)); + } + + add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0))); + + List<String> repositoryUrls = new ArrayList<String>(); + if (gitServlet) { + // add the Gitblit repository url + repositoryUrls.add(BasePage.getRepositoryUrl(entry)); + } + repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(entry.name)); + + String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0); + add(new RepositoryUrlPanel("repositoryCloneUrl", primaryUrl)); + } +} diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html index 5da43e0..6c19fc5 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.html +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html @@ -72,7 +72,7 @@ <wicket:fragment wicket:id="groupRepositoryRow"> <td colspan="1"><span wicket:id="groupName">[group name]</span></td> - <td colspan="6"><span class="hidden-phone" style="font-weight:normal;color:#666;" wicket:id="groupDescription">[description]</span></td> + <td colspan="6" style="padding: 2px;"><span class="hidden-phone" style="font-weight:normal;color:#666;" wicket:id="groupDescription">[description]</span></td> </wicket:fragment> <wicket:fragment wicket:id="repositoryRow"> diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java index a113e00..0855780 100644 --- a/src/com/gitblit/wicket/panels/RepositoriesPanel.java +++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java @@ -58,6 +58,7 @@ import com.gitblit.wicket.pages.ProjectPage; import com.gitblit.wicket.pages.RepositoriesPage; import com.gitblit.wicket.pages.SummaryPage; +import com.gitblit.wicket.pages.UserPage; public class RepositoriesPanel extends BasePanel { @@ -116,7 +117,7 @@ } Map<String, ProjectModel> projects = new HashMap<String, ProjectModel>(); - for (ProjectModel project : GitBlit.self().getProjectModels(user)) { + for (ProjectModel project : GitBlit.self().getProjectModels(user, true)) { projects.put(project.name, project); } List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>(); @@ -138,7 +139,7 @@ final String baseUrl = WicketUtils.getGitblitURL(getRequest()); final boolean showSwatch = GitBlit.getBoolean(Keys.web.repositoryListSwatches, true); - + DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) { private static final long serialVersionUID = 1L; int counter; @@ -156,8 +157,19 @@ currGroupName = entry.name; Fragment row = new Fragment("rowContent", "groupRepositoryRow", this); item.add(row); - row.add(new LinkPanel("groupName", null, entry.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name))); - row.add(new Label("groupDescription", entry.description == null ? "":entry.description)); + + String name = entry.toString(); + if (name.charAt(0) == '~') { + // user page + String username = name.substring(1); + UserModel user = GitBlit.self().getUserModel(username); + row.add(new LinkPanel("groupName", null, user == null ? username : user.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(username))); + row.add(new Label("groupDescription", getString("gb.personalRepositories"))); + } else { + // project page + row.add(new LinkPanel("groupName", null, name, ProjectPage.class, WicketUtils.newProjectParameter(name))); + row.add(new Label("groupDescription", entry.description == null ? "":entry.description)); + } WicketUtils.setCssClass(item, "group"); // reset counter so that first row is light background counter = 0; @@ -272,7 +284,8 @@ WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange)); boolean showOwner = user != null && user.username.equalsIgnoreCase(entry.owner); - if (showAdmin) { + boolean myPersonalRepository = showOwner && entry.isUsersPersonalRepository(user.username); + if (showAdmin || myPersonalRepository) { Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryAdminLinks", this); repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", diff --git a/src/com/gitblit/wicket/panels/SearchPanel.java b/src/com/gitblit/wicket/panels/SearchPanel.java index eab3aea..9d38ab0 100644 --- a/src/com/gitblit/wicket/panels/SearchPanel.java +++ b/src/com/gitblit/wicket/panels/SearchPanel.java @@ -47,7 +47,8 @@ private boolean hasMore; public SearchPanel(String wicketId, final String repositoryName, final String objectId, - final String value, Constants.SearchType searchType, Repository r, int limit, int pageOffset) { + final String value, Constants.SearchType searchType, Repository r, int limit, int pageOffset, + boolean showRemoteRefs) { super(wicketId); boolean pageResults = limit <= 0; int itemsPerPage = GitBlit.getInteger(Keys.web.itemsPerPage, 50); @@ -57,7 +58,7 @@ RevCommit commit = JGitUtils.getCommit(r, objectId); - final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r); + final Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(r, showRemoteRefs); List<RevCommit> commits; if (pageResults) { // Paging result set -- Gitblit v1.9.1