Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Preliminary implementation of server-side forking (issue 137)

The fork mechanism clones the repository , access restrictions, and
other config options.  The app has been updated throughout to handle
personal repositories and to properly display origin/fork links.

In order to fork a repository the user account must have the #fork role,
the origin repository must permit forking, and the user account must
have standard clone permissions to the repository.

Because forking introduces a new user role no existing user accounts can
automatically begin forking a repository.  This is both a pro and a con.

Since the fork has the same access restrictions as the origin repository,
those who can access the origin may also access the fork.  This is intentional
to facilitate integration-manager workflow.  The fork owner does have the
power to completely change the access restrictions of his/her fork.
  • Loading branch information...
commit 1e1b85270f93b3bca624c99b478f3a9a23be2395 1 parent 0d531b1
@gitblit authored
Showing with 1,235 additions and 301 deletions.
  1. +23 −23 .project
  2. +9 −4 docs/04_releases.mkd
  3. +0 −1  docs/05_roadmap.mkd
  4. +46 −0 resources/gitblit.css
  5. +4 −0 src/com/gitblit/ConfigUserService.java
  6. +2 −0  src/com/gitblit/Constants.java
  7. +5 −0 src/com/gitblit/FileUserService.java
  8. +165 −20 src/com/gitblit/GitBlit.java
  9. +1 −1  src/com/gitblit/SyndicationServlet.java
  10. +4 −0 src/com/gitblit/models/ProjectModel.java
  11. +49 −0 src/com/gitblit/models/RepositoryModel.java
  12. +29 −0 src/com/gitblit/models/UserModel.java
  13. +1 −1  src/com/gitblit/utils/ActivityUtils.java
  14. +14 −0 src/com/gitblit/utils/JGitUtils.java
  15. +28 −1 src/com/gitblit/utils/StringUtils.java
  16. +4 −0 src/com/gitblit/wicket/GitBlitWebApp.java
  17. +15 −1 src/com/gitblit/wicket/GitBlitWebApp.properties
  18. +4 −5 src/com/gitblit/wicket/pages/BasePage.java
  19. +3 −1 src/com/gitblit/wicket/pages/EditRepositoryPage.html
  20. +1 −0  src/com/gitblit/wicket/pages/EditRepositoryPage.java
  21. +3 −2 src/com/gitblit/wicket/pages/EditUserPage.html
  22. +1 −0  src/com/gitblit/wicket/pages/EditUserPage.java
  23. +24 −0 src/com/gitblit/wicket/pages/ForksPage.html
  24. +133 −0 src/com/gitblit/wicket/pages/ForksPage.java
  25. +1 −1  src/com/gitblit/wicket/pages/GitSearchPage.java
  26. +1 −1  src/com/gitblit/wicket/pages/HistoryPage.java
  27. +1 −1  src/com/gitblit/wicket/pages/LogPage.java
  28. +4 −66 src/com/gitblit/wicket/pages/ProjectPage.html
  29. +12 −140 src/com/gitblit/wicket/pages/ProjectPage.java
  30. +8 −0 src/com/gitblit/wicket/pages/ProjectsPage.java
  31. +26 −6 src/com/gitblit/wicket/pages/RepositoryPage.html
  32. +109 −12 src/com/gitblit/wicket/pages/RepositoryPage.java
  33. +9 −0 src/com/gitblit/wicket/pages/RootPage.java
  34. +1 −1  src/com/gitblit/wicket/pages/SummaryPage.java
  35. +44 −0 src/com/gitblit/wicket/pages/UserPage.html
  36. +146 −0 src/com/gitblit/wicket/pages/UserPage.java
  37. +1 −1  src/com/gitblit/wicket/panels/GravatarImage.java
  38. +2 −2 src/com/gitblit/wicket/panels/HistoryPanel.java
  39. +2 −2 src/com/gitblit/wicket/panels/LogPanel.java
  40. +79 −0 src/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
  41. +199 −0 src/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
  42. +1 −1  src/com/gitblit/wicket/panels/RepositoriesPanel.html
  43. +18 −5 src/com/gitblit/wicket/panels/RepositoriesPanel.java
  44. +3 −2 src/com/gitblit/wicket/panels/SearchPanel.java
View
46 .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>
View
13 docs/04_releases.mkd
@@ -17,10 +17,15 @@ If you are updating from an earlier release AND you have indexed branches with t
#### 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
View
1  docs/05_roadmap.mkd
@@ -26,7 +26,6 @@ This list is volatile.
### 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.*
View
46 resources/gitblit.css
@@ -20,6 +20,11 @@ a:focus {
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 @@ navbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.navbar d
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;
View
4 src/com/gitblit/ConfigUserService.java
@@ -750,6 +750,9 @@ private synchronized void write() throws IOException {
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 @@ protected synchronized void read() {
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
View
2  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";
View
5 src/com/gitblit/FileUserService.java
@@ -234,6 +234,8 @@ public UserModel getUserModel(String username) {
// 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 @@ public boolean updateUserModel(String username, UserModel model) {
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);
}
View
185 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 @@ public boolean deleteTeam(String teamname) {
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 @@ private void addToCachedRepositoryList(String name, RepositoryModel model) {
* 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 @@ public RepositoryModel getRepositoryModel(String repositoryName) {
if (model == null) {
return null;
}
- addToCachedRepositoryList(repositoryName, model);
+ addToCachedRepositoryList(repositoryName, model);
return model;
}
@@ -1034,7 +1045,7 @@ public RepositoryModel getRepositoryModel(String repositoryName) {
* @return project config map
*/
private Map<String, ProjectModel> getProjectConfigs() {
- if (projectConfigs.isOutdated()) {
+ if (projectCache.isEmpty() || projectConfigs.isOutdated()) {
try {
projectConfigs.load();
@@ -1077,9 +1088,10 @@ public RepositoryModel getRepositoryModel(String repositoryName) {
* 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 @@ public RepositoryModel getRepositoryModel(String repositoryName) {
}
// 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 @@ public RepositoryModel getRepositoryModel(String repositoryName) {
* @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 @@ public ProjectModel getProjectModel(String name) {
Map<String, ProjectModel> configs = getProjectConfigs();
ProjectModel project = configs.get(name.toLowerCase());
if (project == null) {
- return null;
+ 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);
}
- // clone the object
- project = DeepCopier.copy(project);
- String folder = name.toLowerCase() + "/";
- for (String repository : getRepositoryList()) {
- if (repository.toLowerCase().startsWith(folder)) {
- project.addRepository(repository);
+ 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 @@ private RepositoryModel loadRepositoryModel(String repositoryName) {
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 @@ private RepositoryModel loadRepositoryModel(String repositoryName) {
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 @@ public void updateRepositoryModel(String repositoryName, RepositoryModel reposit
"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 @@ public void updateConfiguration(Repository r, RepositoryModel repository) {
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 @@ public boolean deleteRepository(String repositoryName) {
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 @@ public void contextDestroyed(ServletContextEvent contextEvent) {
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;
+ }
}
View
2  src/com/gitblit/SyndicationServlet.java
@@ -227,7 +227,7 @@ private void processRequest(javax.servlet.http.HttpServletRequest request,
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) {
View
4 src/com/gitblit/models/ProjectModel.java
@@ -53,6 +53,10 @@ public ProjectModel(String name, boolean isRoot) {
this.title = "";
this.description = "";
}
+
+ public boolean isUserProject() {
+ return name.charAt(0) == '~';
+ }
public boolean hasRepository(String name) {
return repositories.contains(name.toLowerCase());
View
49 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 @@ public RepositoryModel(String name, String description, String owner, Date lastc
}
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 String toString() {
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;
+ }
}
View
29 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 @@ public boolean hasTeamAccess(String repositoryName) {
}
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());
View
2  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;
View
14 src/com/gitblit/utils/JGitUtils.java
@@ -1369,9 +1369,23 @@ public static boolean deleteBranchRef(Repository repository, String branch) {
* @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>());
View
29 src/com/gitblit/utils/StringUtils.java
@@ -367,7 +367,7 @@ public static String getRelativePath(String basePath, String fullPath) {
* @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 @@ public static String convertOctal(String input) {
}
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;
+ }
}
View
4 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 @@ public void init() {
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) {
View
16 src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -318,4 +318,18 @@ gb.clearCache = clear cache
gb.projects = projects
gb.project = project
gb.allProjects = all projects
-gb.copyToClipboard = copy to clipboard
+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
View
9 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 @@ protected String getServerName() {
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 String getRepositoryUrl(RepositoryModel repository) {
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;
}
View
4 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" /> &nbsp;<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> &nbsp;<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> &nbsp;<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>
View
1  src/com/gitblit/wicket/pages/EditRepositoryPage.java
@@ -343,6 +343,7 @@ protected void onSubmit() {
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"));
View
5 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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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" /> &nbsp;<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> &nbsp;<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" /> &nbsp; <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" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="10" /></div></td></tr>
</tbody>
</table>
</form>
View
1  src/com/gitblit/wicket/pages/EditUserPage.java
@@ -231,6 +231,7 @@ protected void onSubmit() {
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));
View
24 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>
View
133 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", "&nbsp;").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");
+ }
+}
View
2  src/com/gitblit/wicket/pages/GitSearchPage.java
@@ -36,7 +36,7 @@ public GitSearchPage(PageParameters params) {
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);
View
2  src/com/gitblit/wicket/pages/HistoryPage.java
@@ -32,7 +32,7 @@ public HistoryPage(PageParameters params) {
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);
View
2  src/com/gitblit/wicket/pages/LogPage.java
@@ -37,7 +37,7 @@ public LogPage(PageParameters params) {
refid = getRepositoryModel().HEAD;
}
LogPanel logPanel = new LogPanel("logPanel", repositoryName, refid, getRepository(), -1,
- pageNumber - 1);
+ pageNumber - 1, getRepositoryModel().showRemoteBranches);
boolean hasMore = logPanel.hasMore();
add(logPanel);
View
70 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 -->
View
152 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 @@ public int compare(RepositoryModel o1, RepositoryModel o2) {
}
});
- 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", "&nbsp;").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 void addDropDownMenus(List<PageRegistration> pages) {
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 @@ private ProjectModel getProjectModel(String name) {
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) {
View
8 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 @@ public ProjectsPage(PageParameters params) {
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("", "");
View
32 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="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">
- <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>&nbsp;<span class="hidden-phone" wicket:id="workingCopy" style="font-weight:bold;">[working copy]</span></span>
- </p>
+ <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>&nbsp;<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>&nbsp;<span class="hidden-phone" wicket:id="forksProhibited" style="font-weight:bold;">[forks prohibited]</span></span>
+ </div>
+ </wicket:fragment>
+
</wicket:extend>
</body>
</html>
View
121 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 @@ public RepositoryPage(PageParameters params) {
// 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 @@ public RepositoryPage(PageParameters params) {
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 @@ public RepositoryPage(PageParameters params) {
@Override
protected void setupPage(String repositoryName, String pageName) {
- add(new LinkPanel("repositoryName", null, StringUtils.stripDotGit(repositoryName),
- SummaryPage.class, WicketUtils.newRepositoryParameter(repositoryName)));
+ 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 {
+ // 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("workingCopy").setVisible(false));
+ add(new Label("workingCopyIndicator").setVisible(false));
} else {
- Fragment fragment = new Fragment("workingCopy", "workingCopyFragment", this);
+ 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 String getShortObjectId(String objectId) {
}
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) {
View
9 src/com/gitblit/wicket/pages/RootPage.java
@@ -184,6 +184,9 @@ private PageParameters getRootPageParameters() {
// 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 @@ protected void addDropDownMenus(List<PageRegistration> pages) {
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);
View
2  src/com/gitblit/wicket/pages/SummaryPage.java
@@ -130,7 +130,7 @@ public SummaryPage(PageParameters params) {
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());
View
44 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>
View
146 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);
+ }
+}
View
2  src/com/gitblit/wicket/panels/GravatarImage.java
@@ -48,7 +48,7 @@ public GravatarImage(String id, PersonIdent person) {