From 8c92559a483cf0b01b33d926421ec17605b5ff75 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 03 May 2013 19:27:25 -0400
Subject: [PATCH] Partially merged url panel with optional support for app clone urls

---
 src/main/java/com/gitblit/wicket/panels/BasePanel.java              |   14 +
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java     |  227 ++++++++++++++++++++++++++--
 src/main/distrib/data/gitblit.properties                            |   19 +
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties           |    5 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java     |   22 --
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java |   16 -
 src/main/java/com/gitblit/wicket/pages/BasePage.java                |   27 ---
 src/main/java/com/gitblit/GitBlitServer.java                        |   16 +-
 /dev/null                                                           |   52 ------
 src/main/java/com/gitblit/wicket/pages/SummaryPage.java             |   34 ---
 releases.moxie                                                      |    7 
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html     |   30 +--
 src/main/java/com/gitblit/wicket/pages/SummaryPage.html             |    4 
 13 files changed, 272 insertions(+), 201 deletions(-)

diff --git a/releases.moxie b/releases.moxie
index 7c0ec8f..53aef4b 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -26,6 +26,7 @@
 	 - Use standard ServletRequestWrapper instead of custom wrapper (issue 224)
 
 	changes:
+	 - Improved the repository url display.  This display now indicates your repository access permission, per-protocol.
 	 - Improve Gerrit change ref decoration in the refs panel (issue 206)
  	 - Disable Gson's pretty printing which has a huge performance gain
 	 - Properly set application/json content-type on api calls
@@ -33,7 +34,8 @@
 	 - Updated Japanese translation
 	 
     additions: 
-	 - Added GO http/https connector thread pool size setting
+	 - Added 3rd-party app clone links for SourceTree and SparkleShare
+	 - Added GO http/https connector thread pool size setting
 	 - Added a server setting to force a particular translation/Locale for all sessions
 	 - Added smart Git Daemon serving.  If enabled, git:// access will be offered for any repository which permits anonymous access.  If the repository permits anonymous cloning, anonymous git:// clone will be permitted while anonmymous git:// pushes will be rejected.
 	 - Option to automatically tag branch tips on each push with an incremental revision number
@@ -81,8 +83,9 @@
 	- { name: 'git.daemonBindInterface', defaultValue: 'localhost' }
 	- { name: 'git.daemonPort', defaultValue: 0 }
     - { name: 'git.defaultIncrementalPushTagPrefix', defaultValue: 'r' }
+	- { name: 'web.allowAppCloneLinks', defaultValue: true }
 	- { name: 'web.forceDefaultLocale', defaultValue: ' ' }
-	- { name: 'server.nioThreadPoolSize', defaultValue: 50 }
+	- { name: 'server.nioThreadPoolSize', defaultValue: 50 }
 }
 
 #
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index 43185f5..bfdc3e8 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -766,6 +766,11 @@
 # SINCE 0.5.0
 web.otherUrls = 
 
+# Should app-specific clone links be displayed for SourceTree, SparkleShare, etc?
+#
+# SINCE 1.3.0
+web.allowAppCloneLinks = true
+
 # Choose how to present the repositories list.
 #   grouped = group nested/subfolder repositories together (no sorting)
 #   flat = flat list of repositories (sorting allowed)
@@ -1250,13 +1255,13 @@
 # RESTART REQUIRED
 server.useNio = true
 
-# If using Jetty NIO connectors, specify the maximum number of concurrent
-# http/https worker threads to allow.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-server.nioThreadPoolSize = 50
-
+# If using Jetty NIO connectors, specify the maximum number of concurrent
+# http/https worker threads to allow. 
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+server.nioThreadPoolSize = 50
+
 # Context path for the GO application.  You might want to change the context
 # path if running Gitblit behind a proxy layer such as mod_proxy.
 #
diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java
index a41b8a2..2c558be 100644
--- a/src/main/java/com/gitblit/GitBlitServer.java
+++ b/src/main/java/com/gitblit/GitBlitServer.java
@@ -203,7 +203,7 @@
 
 		// conditionally configure the http connector
 		if (params.port > 0) {
-			Connector httpConnector = createConnector(params.useNIO, settings.getInteger(Keys.server.nioThreadPoolSize, 50), params.port);
+			Connector httpConnector = createConnector(params.useNIO, settings.getInteger(Keys.server.nioThreadPoolSize, 50), params.port);
 			String bindInterface = settings.getString(Keys.server.httpBindInterface, null);
 			if (!StringUtils.isEmpty(bindInterface)) {
 				logger.warn(MessageFormat.format("Binding connector on port {0,number,0} to {1}",
@@ -262,7 +262,7 @@
 
 			if (serverKeyStore.exists()) {		        
 				Connector secureConnector = createSSLConnector(params.alias, serverKeyStore, serverTrustStore, params.storePassword,
-						caRevocationList, params.useNIO, settings.getInteger(Keys.server.nioThreadPoolSize, 50), params.securePort, params.requireClientCertificates);
+						caRevocationList, params.useNIO, settings.getInteger(Keys.server.nioThreadPoolSize, 50), params.securePort, params.requireClientCertificates);
 				String bindInterface = settings.getString(Keys.server.httpsBindInterface, null);
 				if (!StringUtils.isEmpty(bindInterface)) {
 					logger.warn(MessageFormat.format(
@@ -410,16 +410,16 @@
 	 * 
 	 * @param useNIO
 	 * @param port
-	 * @param maxThreads
+	 * @param maxThreads
 	 * @return an http connector
 	 */
-	private Connector createConnector(boolean useNIO, int port, int maxThreads) {
+	private Connector createConnector(boolean useNIO, int port, int maxThreads) {
 		Connector connector;
 		if (useNIO) {
 			logger.info("Setting up NIO SelectChannelConnector on port " + port);
 			SelectChannelConnector nioconn = new SelectChannelConnector();
 			nioconn.setSoLingerTime(-1);
-			nioconn.setThreadPool(new QueuedThreadPool(maxThreads));
+			nioconn.setThreadPool(new QueuedThreadPool(maxThreads));
 			connector = nioconn;
 		} else {
 			logger.info("Setting up SocketConnector on port " + port);
@@ -444,13 +444,13 @@
 	 * @param storePassword
 	 * @param caRevocationList
 	 * @param useNIO
-	 * @param nioThreadPoolSize
+	 * @param nioThreadPoolSize
 	 * @param port
 	 * @param requireClientCertificates
 	 * @return an https connector
 	 */
 	private Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore,
-			String storePassword, File caRevocationList, boolean useNIO,  int nioThreadPoolSize, int port,
+			String storePassword, File caRevocationList, boolean useNIO,  int nioThreadPoolSize, int port,
 			boolean requireClientCertificates) {
 		GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias,
 				keyStore, clientTrustStore, storePassword, caRevocationList);
@@ -464,7 +464,7 @@
 			} else {
 				factory.setWantClientAuth(true);
 			}
-			ssl.setThreadPool(new QueuedThreadPool(nioThreadPoolSize));
+			ssl.setThreadPool(new QueuedThreadPool(nioThreadPoolSize));
 			connector = ssl;
 		} else {
 			logger.info("Setting up NIO SslSocketConnector on port " + port);
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 049cc40..4752e99 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -452,4 +452,7 @@
 gb.externalPermissions = {0} access permissions for {1} are externally maintained
 gb.viewAccess = You do not have Gitblit read or write access
 gb.yourProtocolPermissionIs = Your {0} access permission for {1} is {2}
-gb.sparkleshareInvite = SparkleShare invite
\ No newline at end of file
+gb.cloneWithSparkleShare = clone with SparkleShare\u2122
+gb.cloneWithSourceTree = clone with SourceTree\u2122
+gb.cloneWithGitHub = clone with GitHub\u2122 for {0}
+gb.cloneWithSmartGit = clone with SmartGit\u2122
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java
index b3b0767..19fa749 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -57,7 +57,6 @@
 import com.gitblit.Constants.FederationStrategy;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
-import com.gitblit.SparkleShareInviteServlet;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TeamModel;
@@ -311,32 +310,6 @@
 			// git daemon is not running
 			return new Label(wicketId).setVisible(false);
 		}
-	}
-
-	protected String getSparkleShareInviteUrl(RepositoryModel repository) {
-		if (repository.isBare && repository.isSparkleshared()) {
-			UserModel user = GitBlitWebSession.get().getUser();
-			if (user == null) {
-				user = UserModel.ANONYMOUS;
-			}
-			String username = null;
-			if (UserModel.ANONYMOUS != user) {
-				username = user.username;
-			}
-			if (GitBlit.getBoolean(Keys.git.enableGitServlet, true) || (GitBlit.getInteger(Keys.git.daemonPort, 0) > 0)) {
-				// Gitblit as server
-				// ensure user can rewind
-				if (user.canRewindRef(repository)) {
-					String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
-					return SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
-				}
-			} else {
-				// Gitblit as viewer, assume RW+ permission
-				String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
-				return SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
-			}
-		}
-		return null;
 	}
 
 	protected List<ProjectModel> getProjectModels() {
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
index a2264c4..f7e8848 100644
--- a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
@@ -16,22 +16,17 @@
 package com.gitblit.wicket.pages;
 
 import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.List;
 
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 
-import com.gitblit.Constants.AccessPermission;
 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.wicket.GitBlitWebSession;
 import com.gitblit.wicket.GitblitRedirectException;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.DetailedRepositoryUrlPanel;
+import com.gitblit.wicket.panels.RepositoryUrlPanel;
 
 public class EmptyRepositoryPage extends RootPage {
 
@@ -53,24 +48,17 @@
 		
 		setupPage(repositoryName, getString("gb.emptyRepository"));
 
-		List<String> repositoryUrls = new ArrayList<String>();
-
-		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
-			// add the Gitblit repository url
-			repositoryUrls.add(getRepositoryUrl(repository));
-		}
 		UserModel user = GitBlitWebSession.get().getUser();
 		if (user == null) {
 			user = UserModel.ANONYMOUS;
 		}
-		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(repositoryName, UserModel.ANONYMOUS.equals(user) ? "" : user.username));
 		
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.get(0);
-		AccessPermission accessPermission = user.getRepositoryPermission(repository).permission;
+		RepositoryUrlPanel urlPanel = new RepositoryUrlPanel("pushurl", false, user, repository, getLocalizer(), this);
+		String primaryUrl = urlPanel.getPrimaryUrl();
 		
 		add(new Label("repository", repositoryName));
-		add(new DetailedRepositoryUrlPanel("pushurl", getLocalizer(), this, repository.name, primaryUrl, accessPermission));
-		add(new Label("cloneSyntax", MessageFormat.format("git clone {0}", repositoryUrls.get(0))));
+		add(urlPanel);
+		add(new Label("cloneSyntax", MessageFormat.format("git clone {0}", primaryUrl)));
 		add(new Label("remoteSyntax", MessageFormat.format("git remote add gitblit {0}\ngit push gitblit master", primaryUrl)));
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
index c9bce40..a20ad2f 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
@@ -21,12 +21,10 @@
 				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
 				<tr><th style="vertical-align:top;padding-top:4px;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message>&nbsp;<img style="vertical-align: top;padding-left:3px;" wicket:id="accessRestrictionIcon" /></th>
 				    <td style="padding-top:4px;">
-				    	<div wicket:id="repositoryPrimaryUrl">[repository primary url]</div>
-				    	<div wicket:id="repositoryGitDaemonUrl">[repository git daemon url]</div>
+				    	<div wicket:id="repositoryUrlPanel">[repository url panel]</div>
 				    	<div wicket:id="otherUrls" >
 				    		<div wicket:id="otherUrl" style="padding-top:10px"></div>
 				    	</div>
-				    	<div wicket:id="repositorySparkleShareInviteUrl">[repository sparkleshare invite url]</div>
 				    </td>
 				</tr>
 			</table>
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
index f092a38..7588a93 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -42,7 +42,6 @@
 import org.wicketstuff.googlecharts.MarkerType;
 import org.wicketstuff.googlecharts.ShapeMarker;
 
-import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
@@ -50,7 +49,6 @@
 import com.gitblit.models.PathModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
-import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
@@ -60,7 +58,7 @@
 import com.gitblit.wicket.panels.DetailedRepositoryUrlPanel;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.LogPanel;
-import com.gitblit.wicket.panels.SparkleShareInvitePanel;
+import com.gitblit.wicket.panels.RepositoryUrlPanel;
 import com.gitblit.wicket.panels.TagsPanel;
 
 public class SummaryPage extends RepositoryPage {
@@ -127,11 +125,7 @@
 		add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
 				WicketUtils.newRepositoryParameter(repositoryName)));
 
-		List<String> repositoryUrls = new ArrayList<String>();
-
-		AccessPermission accessPermission = null;
 		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {			
-			accessPermission = user.getRepositoryPermission(model).permission;
 			AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction;
 			switch (accessRestriction) {
 			case NONE:
@@ -152,32 +146,14 @@
 			default:
 				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
 			}
-			// add the Gitblit repository url
-			repositoryUrls.add(getRepositoryUrl(getRepositoryModel()));
 		} else {
 			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
 		}
-		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(repositoryName, UserModel.ANONYMOUS.equals(user) ? "" : user.username));
 		
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0);
-		add(new DetailedRepositoryUrlPanel("repositoryPrimaryUrl", getLocalizer(), this, model.name, primaryUrl, accessPermission));
-
-		Component gitDaemonUrlPanel = createGitDaemonUrlPanel("repositoryGitDaemonUrl", user, model);
-		if (!StringUtils.isEmpty(primaryUrl) && gitDaemonUrlPanel instanceof DetailedRepositoryUrlPanel) {
-			WicketUtils.setCssStyle(gitDaemonUrlPanel, "padding-top: 10px");
-		}
-		add(gitDaemonUrlPanel);
-		
-		String sparkleshareUrl = getSparkleShareInviteUrl(model);
-		if (StringUtils.isEmpty(sparkleshareUrl)) {
-			add(new Label("repositorySparkleShareInviteUrl").setVisible(false));
-		} else {
-			Component sparklesharePanel = new SparkleShareInvitePanel("repositorySparkleShareInviteUrl", getLocalizer(), this, sparkleshareUrl, accessPermission);
-			WicketUtils.setCssStyle(sparklesharePanel, "padding-top: 10px;");
-			add(sparklesharePanel);
-		}
-
-		ListDataProvider<String> urls = new ListDataProvider<String>(repositoryUrls);
+		add(new RepositoryUrlPanel("repositoryUrlPanel", false, user, model, getLocalizer(), this));
+				
+		List<String> otherUrls = GitBlit.self().getOtherCloneUrls(repositoryName, UserModel.ANONYMOUS.equals(user) ? "" : user.username);
+		ListDataProvider<String> urls = new ListDataProvider<String>(otherUrls);
 		DataView<String> otherUrlsView = new DataView<String>("otherUrls", urls) {
 			private static final long serialVersionUID = 1L;
 
diff --git a/src/main/java/com/gitblit/wicket/panels/BasePanel.java b/src/main/java/com/gitblit/wicket/panels/BasePanel.java
index ec87917..e241a43 100644
--- a/src/main/java/com/gitblit/wicket/panels/BasePanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/BasePanel.java
@@ -22,6 +22,7 @@
 import org.apache.wicket.Component;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.Model;
+import org.apache.wicket.protocol.http.request.WebClientInfo;
 
 import com.gitblit.Constants;
 import com.gitblit.GitBlit;
@@ -57,6 +58,19 @@
 		}
 		return timeUtils;
 	}
+	
+	protected boolean isWindows() {
+		return isPlatform("windows");
+	}
+
+	protected boolean isMac() {
+		return isPlatform("macintosh");
+	}
+	
+	protected boolean isPlatform(String platform) {
+		String ua = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();
+		return ua.toLowerCase().contains(platform);
+	}
 
 	protected void setPersonSearchTooltip(Component component, String value, Constants.SearchType searchType) {
 		if (searchType.equals(Constants.SearchType.AUTHOR)) {
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
index 822a9b2..7cce74f 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -16,8 +16,6 @@
 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;
@@ -29,7 +27,6 @@
 import org.apache.wicket.markup.html.link.Link;
 import org.apache.wicket.markup.html.panel.Fragment;
 
-import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
@@ -40,7 +37,6 @@
 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;
@@ -58,7 +54,6 @@
 		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
@@ -217,15 +212,6 @@
 
 		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, UserModel.ANONYMOUS.equals(user) ? "" : user.username));
-
-		AccessPermission ap = user.getRepositoryPermission(entry).permission;
-		String primaryUrl = ArrayUtils.isEmpty(repositoryUrls) ? "" : repositoryUrls.remove(0);
-		add(new DetailedRepositoryUrlPanel("repositoryPrimaryUrl",localizer, parent, entry.name, primaryUrl, ap));
+		add(new RepositoryUrlPanel("repositoryPrimaryUrl", true, user, entry, localizer, parent));
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
index d7c76f1..c32d9d8 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
@@ -5,26 +5,14 @@
       lang="en"> 
 
 <wicket:panel>
-	<span wicket:id="repositoryUrl" style="color: blue;">[repository url]</span><span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
-    
-    <!-- Plain JavaScript manual copy & paste -->
-    <wicket:fragment wicket:id="jsPanel">
-    	<span style="vertical-align:baseline;">
-    		<img wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard"></img>
-    	</span>
-    </wicket:fragment>
-    
-    <!-- flash-based button-press copy & paste -->
-    <wicket:fragment wicket:id="clippyPanel">
-   		<object wicket:message="title:gb.copyToClipboard" style="vertical-align:middle;"
-   			wicket:id="clippy"
-   			width="14" 
-   			height="14"
-   			bgcolor="#ffffff" 
-       		quality="high"
-       		wmode="transparent"
-       		scale="noscale"
-       		allowScriptAccess="always"></object>
-	</wicket:fragment>
+	<div wicket:id="repositoryPrimaryUrl">[repository primary url]</div>
+	<div style="padding-top: 2px;">
+		<span class="link" wicket:id="appCloneLink">
+			<span wicket:id="icon"></span>
+			<span wicket:id="link"></span>
+			<span wicket:id="separator" style="padding: 0px 5px 0px 5px;"></span>
+		</span>
+	</div>	
+	<div wicket:id="repositoryGitDaemonUrl">[repository git daemon url]</div>
 </wicket:panel>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
index 58df028..3afb7b1 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -15,37 +15,226 @@
  */
 package com.gitblit.wicket.panels;
 
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.image.ContextImage;
-import org.apache.wicket.markup.html.panel.Fragment;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
 
+import org.apache.wicket.Component;
+import org.apache.wicket.Localizer;
+import org.apache.wicket.RequestCycle;
+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.apache.wicket.protocol.http.WebRequest;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
+import com.gitblit.SparkleShareInviteServlet;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
 
+/**
+ * Smart repository url panel which can display multiple Gitblit repository urls
+ * and also supports 3rd party app clone links.
+ * 
+ * @author James Moger
+ *
+ */
 public class RepositoryUrlPanel extends BasePanel {
 
 	private static final long serialVersionUID = 1L;
+	
+	private final String primaryUrl;
 
-	public RepositoryUrlPanel(String wicketId, String url) {
+	public RepositoryUrlPanel(String wicketId, boolean onlyPrimary, UserModel user, 
+			RepositoryModel repository, Localizer localizer, Component owner) {
 		super(wicketId);
-		add(new Label("repositoryUrl", url));
-		if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
-			// clippy: flash-based copy & paste
-			Fragment fragment = new Fragment("copyFunction", "clippyPanel", this);
-			String baseUrl = WicketUtils.getGitblitURL(getRequest());
-			ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
-			clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(url));
-			fragment.add(clippy);
-			add(fragment);
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		List<String> repositoryUrls = new ArrayList<String>();
+
+		AccessPermission accessPermission = null;
+		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
+			accessPermission = user.getRepositoryPermission(repository).permission;
+			repositoryUrls.add(getRepositoryUrl(repository));
+		}
+		repositoryUrls.addAll(GitBlit.self().getOtherCloneUrls(repository.name, UserModel.ANONYMOUS.equals(user) ? "" : user.username));
+		
+		primaryUrl = repositoryUrls.size() == 0 ? "" : repositoryUrls.remove(0);
+
+		add(new DetailedRepositoryUrlPanel("repositoryPrimaryUrl", localizer, owner, repository.name, primaryUrl, accessPermission));
+		
+		if (!onlyPrimary) {
+			Component gitDaemonUrlPanel = createGitDaemonUrlPanel("repositoryGitDaemonUrl", user, repository);
+			if (!StringUtils.isEmpty(primaryUrl) && gitDaemonUrlPanel instanceof DetailedRepositoryUrlPanel) {
+				WicketUtils.setCssStyle(gitDaemonUrlPanel, "padding-top: 10px");
+			}
+			add(gitDaemonUrlPanel);
 		} else {
-			// javascript: manual copy & paste with modal browser prompt dialog
-			Fragment fragment = new Fragment("copyFunction", "jsPanel", this);
-			ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
-			img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", url));
-			fragment.add(img);
-			add(fragment);
+			add(new Label("repositoryGitDaemonUrl").setVisible(false));
+		}
+		
+		final List<AppCloneLink> cloneLinks = new ArrayList<AppCloneLink>();
+		if (user.canClone(repository) && GitBlit.getBoolean(Keys.web.allowAppCloneLinks, true)) {
+			// universal app clone urls
+//			cloneLinks.add(new AppCloneLink(localizer.getString("gb.cloneWithSmartGit", owner),
+//					MessageFormat.format("smartgit://cloneRepo/{0}", primaryUrl),
+//					"Syntevo SmartGit\u2122"));
+
+			if (isWindows()) {
+				// Windows client app clone urls
+				cloneLinks.add(new AppCloneLink(localizer.getString("gb.cloneWithSourceTree", owner),
+						MessageFormat.format("sourcetree://cloneRepo/{0}", primaryUrl),
+						"Atlassian SourceTree\u2122"));
+//				cloneLinks.add(new AppCloneLink(
+//						MessageFormat.format(localizer.getString("gb.cloneWithGitHub", owner), "Windows"),
+//						MessageFormat.format("github-windows://openRepo/{0}", primaryUrl)));
+			} else if (isMac()) {
+				// Mac client app clone urls
+				cloneLinks.add(new AppCloneLink(localizer.getString("gb.cloneWithSourceTree", owner),
+						MessageFormat.format("sourcetree://cloneRepo/{0}", primaryUrl),
+						"Atlassian SourceTree\u2122"));
+//				cloneLinks.add(new AppCloneLink(
+//						MessageFormat.format(localizer.getString("gb.cloneWithGitHub", owner), "Mac"),
+//						MessageFormat.format("github-mac://openRepo/{0}", primaryUrl)));
+			}
+
+			// sparkleshare invite url
+			String sparkleshareUrl = getSparkleShareInviteUrl(user, repository);
+			if (!StringUtils.isEmpty(sparkleshareUrl)) {
+				cloneLinks.add(new AppCloneLink(localizer.getString("gb.cloneWithSparkleShare", owner),
+						sparkleshareUrl, "SparkleShare \u2122", "icon-star"));
+			}
+		}
+
+		// app clone links
+		ListDataProvider<AppCloneLink> appLinks = new ListDataProvider<AppCloneLink>(cloneLinks);
+		DataView<AppCloneLink> appCloneLinks = new DataView<AppCloneLink>("appCloneLink", appLinks) {
+			private static final long serialVersionUID = 1L;
+			int count;
+			
+			public void populateItem(final Item<AppCloneLink> item) {
+				final AppCloneLink appLink = item.getModelObject();
+				item.add(new Label("icon", MessageFormat.format("<i class=\"{0}\"></i>", appLink.icon)).setEscapeModelStrings(false));
+				LinkPanel linkPanel = new LinkPanel("link", null, appLink.name, appLink.url);
+				if (!StringUtils.isEmpty(appLink.tooltip)) {
+					WicketUtils.setHtmlTooltip(linkPanel, appLink.tooltip);
+				}
+				item.add(linkPanel);
+				item.add(new Label("separator", "|").setVisible(count < (cloneLinks.size() - 1)));
+				count++;
+			}
+		};
+		add(appCloneLinks);
+	}
+	
+	public String getPrimaryUrl() {
+		return primaryUrl;
+	}
+	
+	protected String getRepositoryUrl(RepositoryModel repository) {
+		StringBuilder sb = new StringBuilder();
+		sb.append(WicketUtils.getGitblitURL(RequestCycle.get().getRequest()));
+		sb.append(Constants.GIT_PATH);
+		sb.append(repository.name);
+		
+		// inject username into repository url if authentication is required
+		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
+				&& GitBlitWebSession.get().isLoggedIn()) {
+			String username = GitBlitWebSession.get().getUsername();
+			sb.insert(sb.indexOf("://") + 3, username + "@");
+		}
+		return sb.toString();
+	}
+	
+	protected Component createGitDaemonUrlPanel(String wicketId, UserModel user, RepositoryModel repository) {
+		int gitDaemonPort = GitBlit.getInteger(Keys.git.daemonPort, 0);
+		if (gitDaemonPort > 0 && user.canClone(repository)) {
+			String servername = ((WebRequest) getRequest()).getHttpServletRequest().getServerName();
+			String gitDaemonUrl;
+			if (gitDaemonPort == 9418) {
+				// standard port
+				gitDaemonUrl = MessageFormat.format("git://{0}/{1}", servername, repository.name);
+			} else {
+				// non-standard port
+				gitDaemonUrl = MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, gitDaemonPort, repository.name);
+			}
+			
+			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;;
+			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
+				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
+					// can not authenticate clone via anonymous git protocol
+					gitDaemonPermission = AccessPermission.NONE;
+				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+					// can not authenticate push via anonymous git protocol
+					gitDaemonPermission = AccessPermission.CLONE;
+				} else {
+					// normal user permission
+				}
+			}
+			
+			if (AccessPermission.NONE.equals(gitDaemonPermission)) {
+				// repository prohibits all anonymous access
+				return new Label(wicketId).setVisible(false);
+			} else {
+				// repository allows some form of anonymous access
+				return new DetailedRepositoryUrlPanel(wicketId, getLocalizer(), this, repository.name, gitDaemonUrl, gitDaemonPermission);
+			}
+		} else {
+			// git daemon is not running
+			return new Label(wicketId).setVisible(false);
+		}
+	}
+
+	protected String getSparkleShareInviteUrl(UserModel user, RepositoryModel repository) {
+		if (repository.isBare && repository.isSparkleshared()) {
+			String username = null;
+			if (UserModel.ANONYMOUS != user) {
+				username = user.username;
+			}
+			if (GitBlit.getBoolean(Keys.git.enableGitServlet, true) || (GitBlit.getInteger(Keys.git.daemonPort, 0) > 0)) {
+				// Gitblit as server
+				// ensure user can rewind
+				if (user.canRewindRef(repository)) {
+					String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
+					return SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
+				}
+			} else {
+				// Gitblit as viewer, assume RW+ permission
+				String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
+				return SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
+			}
+		}
+		return null;
+	}
+	
+	static class AppCloneLink implements Serializable {
+		
+		private static final long serialVersionUID = 1L;
+		
+		final String name;
+		final String url;
+		final String tooltip;
+		final String icon;
+		
+		public AppCloneLink(String name, String url, String tooltip) {
+			this(name, url, tooltip, "icon-download");
+		}
+		
+		public AppCloneLink(String name, String url, String tooltip, String icon) {
+			this.name = name;
+			this.url = url;
+			this.tooltip = tooltip;
+			this.icon = icon;
 		}
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/panels/SparkleShareInvitePanel.html b/src/main/java/com/gitblit/wicket/panels/SparkleShareInvitePanel.html
deleted file mode 100644
index 483050c..0000000
--- a/src/main/java/com/gitblit/wicket/panels/SparkleShareInvitePanel.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!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>
-   	<span class="repositoryUrlContainer">
-		<span class="repositoryUrlEndCap">
-			<img wicket:id="sparkleshareIcon"></img>
-		</span>
-		<span class="repositoryUrl">
-			<a wicket:id="inviteUrl"><wicket:message key="gb.sparkleshareInvite"></wicket:message></a>
-		</span>
-		<span class="hidden-phone hidden-tablet repositoryUrlEndCap" wicket:id="accessPermission">[access permission]</span>
-	</span>
-</wicket:panel>
-</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/SparkleShareInvitePanel.java b/src/main/java/com/gitblit/wicket/panels/SparkleShareInvitePanel.java
deleted file mode 100644
index 5d7aa58..0000000
--- a/src/main/java/com/gitblit/wicket/panels/SparkleShareInvitePanel.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright 2013 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 org.apache.wicket.Component;
-import org.apache.wicket.Localizer;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.image.ContextImage;
-import org.apache.wicket.markup.html.link.ExternalLink;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.wicket.WicketUtils;
-
-public class SparkleShareInvitePanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-
-	public SparkleShareInvitePanel(String wicketId, Localizer localizer, Component parent, String url, AccessPermission ap) {
-		super(wicketId);
-		ContextImage star = WicketUtils.newImage("sparkleshareIcon", "star_16x16.png");
-		add(star);
-		add(new ExternalLink("inviteUrl", url));
-		String note = localizer.getString("gb.externalAccess", parent);
-		String permission = "";
-		if (ap != null) {
-			permission = ap.toString();
-			if (ap.atLeast(AccessPermission.PUSH)) {
-				note = localizer.getString("gb.readWriteAccess", parent);
-			} else if (ap.atLeast(AccessPermission.CLONE)) {
-				note = localizer.getString("gb.readOnlyAccess", parent);
-			} else {
-				note = localizer.getString("gb.viewAccess", parent);
-			}
-		}
-		Label label = new Label("accessPermission", permission);
-		WicketUtils.setHtmlTooltip(label, note);
-		add(label);
-	}
-}

--
Gitblit v1.9.1