From eb1405f736f2f98e14215774dd53eea9b9a77017 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 01 Oct 2012 20:45:19 -0400
Subject: [PATCH] Show fork links according to user permissions. Improve fork detection.

---
 src/com/gitblit/wicket/pages/ForksPage.java      |   31 +++++--
 src/com/gitblit/GitBlit.java                     |   76 +++++++++++++++++++
 src/com/gitblit/models/UserModel.java            |   10 ++
 src/com/gitblit/wicket/pages/RepositoryPage.java |  110 +++++++++++++++------------
 4 files changed, 168 insertions(+), 59 deletions(-)

diff --git a/src/com/gitblit/GitBlit.java b/src/com/gitblit/GitBlit.java
index 699bbac..f86c66a 100644
--- a/src/com/gitblit/GitBlit.java
+++ b/src/com/gitblit/GitBlit.java
@@ -32,6 +32,7 @@
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -1325,6 +1326,81 @@
 		r.close();
 		return true;
 	}
+	
+	/**
+	 * Determines if the specified user has a fork of the specified origin
+	 * repository.
+	 * 
+	 * @param username
+	 * @param origin
+	 * @return true the if the user has a fork
+	 */
+	public boolean hasFork(String username, String origin) {
+		return getFork(username, origin) != null;
+	}
+	
+	/**
+	 * Gets the name of a user's fork of the specified origin
+	 * repository.
+	 * 
+	 * @param username
+	 * @param origin
+	 * @return the name of the user's fork, null otherwise
+	 */
+	public String getFork(String username, String origin) {
+		String userProject = "~" + username.toLowerCase();
+		if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
+			String userPath = userProject + "/";
+
+			// collect all origin nodes in fork network
+			Set<String> roots = new HashSet<String>();
+			roots.add(origin);
+			RepositoryModel originModel = repositoryListCache.get(origin);
+			while (originModel != null) {
+				if (!ArrayUtils.isEmpty(originModel.forks)) {
+					for (String fork : originModel.forks) {
+						if (!fork.startsWith(userPath)) {
+							roots.add(fork);
+						}
+					}
+				}
+				
+				if (originModel.originRepository != null) {
+					roots.add(originModel.originRepository);
+					originModel = repositoryListCache.get(originModel.originRepository);
+				} else {
+					// break
+					originModel = null;
+				}
+			}
+			
+			for (String repository : repositoryListCache.keySet()) {
+				if (repository.toLowerCase().startsWith(userPath)) {
+					RepositoryModel model = repositoryListCache.get(repository);
+					if (!StringUtils.isEmpty(model.originRepository)) {
+						if (roots.contains(model.originRepository)) {
+							// user has a fork in this graph
+							return model.name;
+						}
+					}
+				}
+			}
+		} else {
+			// not caching
+			ProjectModel project = getProjectModel(userProject);
+			for (String repository : project.repositories) {
+				if (repository.toLowerCase().startsWith(userProject)) {
+					RepositoryModel model = repositoryListCache.get(repository);
+					if (model.originRepository.equalsIgnoreCase(origin)) {
+						// user has a fork
+						return model.name;
+					}
+				}
+			}
+		}
+		// user does not have a fork
+		return null;
+	}
 
 	/**
 	 * Returns the size in bytes of the repository. Gitblit caches the
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index 0ede878..25787a1 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -86,6 +86,16 @@
 		return false;
 	}
 	
+	public boolean canViewRepository(RepositoryModel repository) {
+		if (canAdmin) {
+			return true;
+		}
+		if (repository.accessRestriction.atLeast(AccessRestrictionType.VIEW)) {
+			return canAccessRepository(repository);
+		}
+		return true;
+	}
+	
 	public boolean canForkRepository(RepositoryModel repository) {
 		if (canAdmin) {
 			return true;
diff --git a/src/com/gitblit/wicket/pages/ForksPage.java b/src/com/gitblit/wicket/pages/ForksPage.java
index 7b8235b..54c2c82 100644
--- a/src/com/gitblit/wicket/pages/ForksPage.java
+++ b/src/com/gitblit/wicket/pages/ForksPage.java
@@ -42,26 +42,32 @@
 	public ForksPage(PageParameters params) {
 		super(params);
 		
+		UserModel user = GitBlitWebSession.get().getUser();
 		RepositoryModel model = getRepositoryModel();
-		RepositoryModel origin;
+		RepositoryModel origin = model;
 		List<String> list;
 		if (ArrayUtils.isEmpty(model.forks)) {
-			// origin repository has forks
-			origin = GitBlit.self().getRepositoryModel(model.originRepository);
-			list = new ArrayList<String>(origin.forks);
+			if (!StringUtils.isEmpty(model.originRepository)) {
+				// try origin repository
+				origin = GitBlit.self().getRepositoryModel(model.originRepository);
+			}
+			if (origin == null || origin.forks == null) {
+				list = new ArrayList<String>();
+			} else {
+				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);
+			UserModel originUser = GitBlit.self().getUserModel(origin.projectPath.substring(1));
+			PersonIdent ident = new PersonIdent(originUser.getDisplayName(), originUser.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)));
+			add(new LinkPanel("forkSourceProject", null, originUser.getDisplayName(), UserPage.class, WicketUtils.newUsernameParameter(originUser.username)));
 		} else {
 			// standard repository
 			add(new GravatarImage("forkSourceAvatar", new PersonIdent("", ""), 20).setVisible(false));
@@ -85,10 +91,15 @@
 		}
 		
 		String source = StringUtils.getLastPathElement(origin.name);
-		add(new LinkPanel("forkSource", null, StringUtils.stripDotGit(source), SummaryPage.class, WicketUtils.newRepositoryParameter(origin.name)));
+		if (user != null && user.canViewRepository(origin)) {
+			// user can view the origin
+			add(new LinkPanel("forkSource", null, StringUtils.stripDotGit(source), SummaryPage.class, WicketUtils.newRepositoryParameter(origin.name)));
+		} else {
+			// user can not view the origin
+			add(new Label("forkSource", StringUtils.stripDotGit(source)));
+		}
 
 		// 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);
diff --git a/src/com/gitblit/wicket/pages/RepositoryPage.java b/src/com/gitblit/wicket/pages/RepositoryPage.java
index a85d21e..8ca2b33 100644
--- a/src/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoryPage.java
@@ -136,22 +136,12 @@
 		pages.put("branches", new PageRegistration("gb.branches", BranchesPage.class, params));
 		pages.put("tags", new PageRegistration("gb.tags", TagsPage.class, params));
 		pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
+		pages.put("forks", new PageRegistration("gb.forks", ForksPage.class, params));
 
 		// conditional links
 		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));
@@ -204,15 +194,29 @@
 				WicketUtils.newRepositoryParameter(repositoryName)));
 		add(new Label("pageName", pageName).setRenderBodyOnly(true));
 		
+		UserModel user = GitBlitWebSession.get().getUser();
+
 		// 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);
+			RepositoryModel origin = GitBlit.self().getRepositoryModel(model.originRepository);
+			if (origin == null) {
+				// no origin repository
+				add(new Label("originRepository").setVisible(false));
+			} else if (!user.canViewRepository(origin)) {
+				// show origin repository without link
+				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
+				forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
+				add(forkFrag);
+			} else {
+				// link to origin repository
+				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) {
@@ -224,33 +228,57 @@
 			wc.add(lbl);
 			add(wc);
 		}
-		
-		if (getRepositoryModel().allowForks) {
+
+		// fork controls
+		if (user == null) {
+			// must be logged-in to fork, hide all fork controls
+			add(new ExternalLink("forkLink", "").setVisible(false));
+			add(new ExternalLink("myForkLink", "").setVisible(false));
 			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) {
+			String fork = GitBlit.self().getFork(user.username, model.name);
+			boolean hasFork = fork != null;
+			boolean canFork = user.canForkRepository(model);
+
+			if (hasFork || !canFork) {
+				// user not allowed to fork or fork already exists or repo forbids forking
+				add(new ExternalLink("forkLink", "").setVisible(false));
+				
+				if (user.canFork && !model.allowForks) {
+					// show forks prohibited indicator
+					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);
+				} else {
+					// can not fork, no need for forks prohibited indicator
+					add(new Label("forksProhibitedIndicator").setVisible(false));
+				}
+				
+				if (hasFork && !fork.equals(model.name)) {
+					// user has fork, view my fork link
+					String url = getRequestCycle().urlFor(SummaryPage.class, WicketUtils.newRepositoryParameter(fork)).toString();
+					add(new ExternalLink("myForkLink", url));
+				} else {
+					// no fork, hide view my fork link
+					add(new ExternalLink("myForkLink", "").setVisible(false));
+				}
+			} else if (canFork) {
+				// can fork and we do not have one
+				add(new Label("forksProhibitedIndicator").setVisible(false));
+				add(new ExternalLink("myForkLink", "").setVisible(false));
 				Link<Void> forkLink = new Link<Void>("forkLink") {
 
 					private static final long serialVersionUID = 1L;
 
 					@Override
 					public void onClick() {
+						UserModel user = GitBlitWebSession.get().getUser();
 						RepositoryModel model = getRepositoryModel();
+						String asFork = MessageFormat.format("~{0}/{1}.git", user.username, StringUtils.stripDotGit(StringUtils.getLastPathElement(model.name)));
 						if (GitBlit.self().fork(model, GitBlitWebSession.get().getUser())) {
-							throw new RedirectException(SummaryPage.class, WicketUtils.newRepositoryParameter(clonedRepo));
+							throw new RedirectException(SummaryPage.class, WicketUtils.newRepositoryParameter(asFork));
 						} else {
 							error(MessageFormat.format(getString("gb.repositoryForkFailed"), model));
 						}
@@ -259,23 +287,7 @@
 				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);
@@ -577,4 +589,4 @@
 			getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
 		}
 	}
-}
+}
\ No newline at end of file

--
Gitblit v1.9.1