"title": "Git",\r
"description": "a fast, open-source, distributed VCS",\r
"legal": "released under the GPLv2 open source license",\r
- "command": "git clone {0}",\r
+ "command": "git clone ${repoUrl}",\r
"productUrl": "http://git-scm.com",\r
"icon": "git-black_32x32.png",\r
"isActive": true\r
"title": "syntevo SmartGit/Hg\u2122",\r
"description": "a Git client for Windows, Mac, & Linux",\r
"legal": "\u00a9 2013 syntevo GmbH. All rights reserved.",\r
- "cloneUrl": "smartgit://cloneRepo/{0}",\r
+ "cloneUrl": "smartgit://cloneRepo/${repoUrl}",\r
"productUrl": "http://www.syntevo.com/smartgithg",\r
"platforms": [ "windows", "macintosh", "linux" ],\r
"icon": "smartgithg_32x32.png",\r
"title": "Atlassian SourceTree\u2122",\r
"description": "a free Git client for Windows or Mac",\r
"legal": "\u00a9 2013 Atlassian. All rights reserved.",\r
- "cloneUrl": "sourcetree://cloneRepo/{0}",\r
+ "cloneUrl": "sourcetree://cloneRepo/${repoUrl}",\r
"productUrl": "http://sourcetreeapp.com",\r
"platforms": [ "windows", "macintosh" ],\r
"icon": "sourcetree_32x32.png",\r
"title": "fournova Tower\u2122",\r
"description": "a Git client for Mac",\r
"legal": "\u00a9 2013 fournova Software GmbH. All rights reserved.",\r
- "cloneUrl": "gittower://openRepo/{0}",\r
+ "cloneUrl": "gittower://openRepo/${repoUrl}",\r
"productUrl": "http://www.git-tower.com",\r
"platforms": [ "macintosh" ],\r
"icon": "tower_32x32.png",\r
"title": "GitHub\u2122 for Macintosh",\r
"description": "a free Git client for Mac OS X",\r
"legal": "\u00a9 2013 GitHub. All rights reserved.",\r
- "cloneUrl": "github-mac://openRepo/{0}",\r
+ "cloneUrl": "github-mac://openRepo/${repoUrl}",\r
"productUrl": "http://mac.github.com",\r
"platforms": [ "macintosh" ],\r
"isActive": false\r
"title": "GitHub\u2122 for Windows",\r
"description": "a free Git client for Windows",\r
"legal": "\u00a9 2013 GitHub. All rights reserved.",\r
- "cloneUrl": "github-windows://openRepo/{0}",\r
+ "cloneUrl": "github-windows://openRepo/${repoUrl}",\r
"productUrl": "http://windows.github.com",\r
"platforms": [ "windows" ],\r
"isActive": false\r
+ },\r
+ {\r
+ "name": "SparkleShare",\r
+ "title": "SparkleShare\u2122",\r
+ "description": "an open source collaboration and sharing tool",\r
+ "legal": "released under the GPLv3 open source license",\r
+ "cloneUrl": "sparkleshare://inviteRepo/${baseUrl}/sparkleshare/${repoUrl}.xml",\r
+ "productUrl": "http://sparkleshare.org",\r
+ "platforms": [ "windows", "macintosh", "linux" ],\r
+ "icon": "sparkleshare_32x32.png",\r
+ "minimumPermission" : "RW+",\r
+ "isActive": false\r
}\r
]
\ No newline at end of file
import com.gitblit.wicket.GitBlitWebSession;\r
import com.gitblit.wicket.WicketUtils;\r
import com.google.gson.Gson;\r
-import com.google.gson.GsonBuilder;\r
import com.google.gson.JsonIOException;\r
import com.google.gson.JsonSyntaxException;\r
import com.google.gson.reflect.TypeToken;\r
Type type = new TypeToken<Collection<GitClientApplication>>() {\r
}.getType();\r
InputStreamReader reader = new InputStreamReader(is);\r
- Gson gson = new GsonBuilder().create();\r
+ Gson gson = JsonUtils.gson();
Collection<GitClientApplication> links = gson.fromJson(reader, type);\r
return links;\r
} catch (JsonIOException e) {\r
\r
import java.io.IOException;\r
import java.text.MessageFormat;\r
-import java.util.List;\r
\r
import javax.servlet.ServletException;\r
import javax.servlet.http.HttpServlet;\r
import javax.servlet.http.HttpServletRequest;\r
import javax.servlet.http.HttpServletResponse;\r
\r
-import com.gitblit.Constants.AccessRestrictionType;\r
import com.gitblit.models.RepositoryModel;\r
import com.gitblit.models.UserModel;\r
import com.gitblit.utils.StringUtils;\r
super();\r
}\r
\r
- /**\r
- * Returns an Sparkleshare invite url to this servlet for the repository.\r
- * https://github.com/hbons/SparkleShare/wiki/Invites\r
- * \r
- * @param baseURL\r
- * @param repository\r
- * @param username\r
- * @return an url\r
- */\r
- public static String asLink(String baseURL, String repository, String username) {\r
- if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {\r
- baseURL = baseURL.substring(0, baseURL.length() - 1);\r
- }\r
- String url = baseURL + Constants.SPARKLESHARE_INVITE_PATH\r
- + ((StringUtils.isEmpty(username) ? "" : (username + "@")))\r
- + repository + ".xml";\r
- url = url.replace("https://", "sparkleshare://");\r
- url = url.replace("http://", "sparkleshare-unsafe://");\r
- return url;\r
- }\r
- \r
@Override\r
protected void doPost(HttpServletRequest request, HttpServletResponse response)\r
throws ServletException, java.io.IOException {\r
java.io.IOException { \r
\r
// extract repo name from request\r
- String path = request.getPathInfo();\r
- if (path != null && path.length() > 1) {\r
- if (path.charAt(0) == '/') {\r
- path = path.substring(1);\r
- }\r
- }\r
+ String repoUrl = request.getPathInfo().substring(1);\r
+\r
// trim trailing .xml\r
- if (path.endsWith(".xml")) {\r
- path = path.substring(0, path.length() - 4);\r
+ if (repoUrl.endsWith(".xml")) {\r
+ repoUrl = repoUrl.substring(0, repoUrl.length() - 4);\r
}\r
\r
+ String servletPath = Constants.GIT_PATH;\r
+ \r
+ int schemeIndex = repoUrl.indexOf("://") + 3;\r
+ String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex)); \r
+ String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length());\r
String username = null;\r
- int fetch = path.indexOf('@');\r
- if (fetch > -1) {\r
- username = path.substring(0, fetch);\r
- path = path.substring(fetch + 1);\r
+ int fetchIndex = repoUrl.indexOf('@');\r
+ if (fetchIndex > -1) {\r
+ username = repoUrl.substring(schemeIndex, fetchIndex);\r
}\r
UserModel user;\r
if (StringUtils.isEmpty(username)) {\r
username = "";\r
}\r
\r
- // ensure that the requested repository exists and is sparkleshared\r
+ // ensure that the requested repository exists\r
RepositoryModel model = GitBlit.self().getRepositoryModel(path);\r
if (model == null) {\r
response.setStatus(HttpServletResponse.SC_NOT_FOUND);\r
response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path));\r
return;\r
- } else if (!model.isSparkleshared()) {\r
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);\r
- response.getWriter().append(MessageFormat.format("Repository \"{0}\" is not sparkleshared!", path));\r
- return;\r
}\r
\r
- if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)\r
- || GitBlit.getInteger(Keys.git.daemonPort, 0) > 0) {\r
- // Gitblit as server\r
- // determine username for repository url\r
- if (model.accessRestriction.exceeds(AccessRestrictionType.NONE)) {\r
- if (!user.canRewindRef(model)) {\r
- response.setStatus(HttpServletResponse.SC_FORBIDDEN);\r
- response.getWriter().append(MessageFormat.format("\"{0}\" does not have RW+ permissions for {1}!", user.username, path));\r
- return;\r
- }\r
- }\r
- \r
- if (model.accessRestriction.exceeds(AccessRestrictionType.NONE)) {\r
- username = user.username + "@";\r
- } else {\r
- username = "";\r
- }\r
-\r
- String serverPort = "";\r
- if (request.getScheme().equals("https")) {\r
- if (request.getServerPort() != 443) {\r
- serverPort = ":" + request.getServerPort();\r
- }\r
- } else if (request.getScheme().equals("http")) {\r
- if (request.getServerPort() != 80) {\r
- serverPort = ":" + request.getServerPort();\r
- }\r
- }\r
-\r
- // assume http/https serving\r
- String scheme = request.getScheme();\r
- String servletPath = Constants.GIT_PATH;\r
-\r
- // try to switch to git://, if git servlet disabled and repo has no restrictions\r
- if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)\r
- && (GitBlit.getInteger(Keys.git.daemonPort, 0) > 0)\r
- && AccessRestrictionType.NONE == model.accessRestriction) {\r
- scheme = "git";\r
- servletPath = "/";\r
- serverPort = GitBlit.getString(Keys.git.daemonPort, "");\r
- }\r
-\r
- // construct Sparkleshare invite\r
- StringBuilder sb = new StringBuilder(); \r
- sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");\r
- sb.append("<sparkleshare><invite>\n");\r
- sb.append(MessageFormat.format("<address>{0}://{1}{2}{3}{4}</address>\n", scheme, username, request.getServerName(), serverPort, request.getContextPath()));\r
- sb.append(MessageFormat.format("<remote_path>{0}{1}</remote_path>\n", servletPath, model.name));\r
- if (GitBlit.getInteger(Keys.fanout.port, 0) > 0) {\r
- // Gitblit is running it's own fanout service for pubsub notifications\r
- sb.append(MessageFormat.format("<announcements_url>tcp://{0}:{1}</announcements_url>\n", request.getServerName(), GitBlit.getString(Keys.fanout.port, "")));\r
- }\r
- sb.append("</invite></sparkleshare>\n");\r
-\r
- // write invite to client\r
- response.setContentType("application/xml");\r
- response.setContentLength(sb.length());\r
- response.getWriter().append(sb.toString());\r
- } else {\r
- // Gitblit as viewer, repository access handled externally so\r
- // assume RW+ permission\r
- List<String> others = GitBlit.getStrings(Keys.web.otherUrls);\r
- if (others.size() == 0) {\r
- return;\r
- }\r
- \r
- String address = MessageFormat.format(others.get(0), "", username);\r
- \r
- StringBuilder sb = new StringBuilder(); \r
- sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");\r
- sb.append("<sparkleshare><invite>\n");\r
- \r
- sb.append(MessageFormat.format("<address>{0}</address>\n", address));\r
- sb.append(MessageFormat.format("<remote_path>{0}</remote_path>\n", model.name));\r
- if (GitBlit.getInteger(Keys.fanout.port, 0) > 0) {\r
- // Gitblit is running it's own fanout service for pubsub notifications\r
- sb.append(MessageFormat.format("<announcements_url>tcp://{0}:{1}</announcements_url>\n", request.getServerName(), GitBlit.getString(Keys.fanout.port, "")));\r
- }\r
- sb.append("</invite></sparkleshare>\n");\r
-\r
- // write invite to client\r
- response.setContentType("application/xml");\r
- response.setContentLength(sb.length());\r
- response.getWriter().append(sb.toString());\r
+ StringBuilder sb = new StringBuilder(); \r
+ sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");\r
+ sb.append("<sparkleshare><invite>\n");\r
+ sb.append(MessageFormat.format("<address>{0}</address>\n", host));\r
+ sb.append(MessageFormat.format("<remote_path>{0}{1}</remote_path>\n", servletPath, model.name));\r
+ if (GitBlit.getInteger(Keys.fanout.port, 0) > 0) {\r
+ // Gitblit is running it's own fanout service for pubsub notifications\r
+ sb.append(MessageFormat.format("<announcements_url>tcp://{0}:{1}</announcements_url>\n", request.getServerName(), GitBlit.getString(Keys.fanout.port, "")));\r
}\r
+ sb.append("</invite></sparkleshare>\n");\r
+\r
+ // write invite to client\r
+ response.setContentType("application/xml");\r
+ response.setContentLength(sb.length());\r
+ response.getWriter().append(sb.toString());\r
}\r
}\r
\r
import java.io.Serializable;\r
\r
+import com.gitblit.Constants.AccessPermission;\r
import com.gitblit.utils.ArrayUtils;\r
import com.gitblit.utils.StringUtils;\r
\r
public String command;\r
public String productUrl;\r
public String[] platforms;\r
+ public AccessPermission minimumPermission;\r
public boolean isActive;\r
\r
public boolean allowsPlatform(String p) {\r
<div class="btn-group repositoryUrlContainer">\r
<img style="vertical-align: middle;padding: 0px 0px 1px 3px;" wicket:id="accessRestrictionIcon"></img>\r
<span wicket:id="menu"></span>\r
- <span class="repositoryUrl">\r
+ <div class="repositoryUrl">\r
<span wicket:id="primaryUrl">[repository primary url]</span>\r
<span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>\r
- </span>\r
+ </div>\r
<span class="hidden-phone hidden-tablet repositoryUrlRightCap" wicket:id="primaryUrlPermission">[repository primary url permission]</span>\r
</div>\r
</div>\r
<wicket:fragment wicket:id="applicationMenusFragment">\r
<div class="btn-toolbar" style="margin: 4px 0px 0px 0px;">\r
<div class="btn-group" wicket:id="appMenus">\r
- <a class="btn btn-mini btn-appmenu" data-toggle="dropdown" href="#"> \r
- <span wicket:id="applicationName"></span>\r
- <span class="caret"></span>\r
- </a>\r
- <ul class="dropdown-menu applicationMenu">\r
- <li>\r
- <div class="applicationHeaderMenuItem">\r
- <div style="float:right">\r
- <img style="padding-right: 5px;vertical-align: middle;" wicket:id="applicationIcon"></img>\r
- </div>\r
- <span class="applicationTitle" wicket:id="applicationTitle"></span>\r
- </div>\r
- </li>\r
- <li><div class="applicationHeaderMenuItem"><span wicket:id="applicationDescription"></span></div></li>\r
- <li><div class="applicationLegalMenuItem"><span wicket:id="applicationLegal"></span></div></li>\r
- \r
- <li class="divider" style="margin: 5px 1px 0px 1px;clear:both;" ></li>\r
- \r
- <li class="action" wicket:id="actionItems">\r
- <span wicket:id="actionItem"></span>\r
- </li>\r
- </ul>\r
+ <span wicket:id="appMenu"></span>\r
</div>\r
</div>\r
</wicket:fragment>\r
\r
+ <wicket:fragment wicket:id="appMenuFragment">\r
+ <a class="btn btn-mini btn-appmenu" data-toggle="dropdown" href="#"> \r
+ <span wicket:id="applicationName"></span>\r
+ <span class="caret"></span>\r
+ </a>\r
+ <ul class="dropdown-menu applicationMenu">\r
+ <li>\r
+ <div class="applicationHeaderMenuItem">\r
+ <div style="float:right">\r
+ <img style="padding-right: 5px;vertical-align: middle;" wicket:id="applicationIcon"></img>\r
+ </div>\r
+ <span class="applicationTitle" wicket:id="applicationTitle"></span>\r
+ </div>\r
+ </li>\r
+ <li><div class="applicationHeaderMenuItem"><span wicket:id="applicationDescription"></span></div></li>\r
+ <li><div class="applicationLegalMenuItem"><span wicket:id="applicationLegal"></span></div></li>\r
+ \r
+ <li class="divider" style="margin: 5px 1px 0px 1px;clear:both;" ></li>\r
+ \r
+ <li class="action" wicket:id="actionItems">\r
+ <span wicket:id="actionItem"></span>\r
+ </li>\r
+ </ul>\r
+ </wicket:fragment>\r
+ \r
<wicket:fragment wicket:id="urlProtocolMenuFragment">\r
<a class="" data-toggle="dropdown" href="#"> \r
<span class="repositoryUrlLeftCap" wicket:id="menuText">URLs</span>\r
import com.gitblit.Constants.AccessRestrictionType;\r
import com.gitblit.GitBlit;\r
import com.gitblit.Keys;\r
-import com.gitblit.SparkleShareInviteServlet;\r
import com.gitblit.models.GitClientApplication;\r
import com.gitblit.models.RepositoryModel;\r
import com.gitblit.models.RepositoryUrl;\r
import com.gitblit.models.UserModel;\r
import com.gitblit.utils.StringUtils;\r
+import com.gitblit.wicket.ExternalImage;\r
import com.gitblit.wicket.GitBlitWebSession;\r
import com.gitblit.wicket.WicketUtils;\r
\r
return urlPanel;\r
}\r
\r
- protected Fragment createApplicationMenus(String wicketId, UserModel user, final RepositoryModel repository, List<RepositoryUrl> repositoryUrls) {\r
+ protected Fragment createApplicationMenus(String wicketId, UserModel user, final RepositoryModel repository, final List<RepositoryUrl> repositoryUrls) {\r
final List<GitClientApplication> displayedApps = new ArrayList<GitClientApplication>();\r
final String userAgent = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();\r
\r
displayedApps.add(app);\r
}\r
}\r
-\r
- GitClientApplication sparkleshare = getSparkleShareAppMenu(user, repository);\r
- if (sparkleshare != null) {\r
- displayedApps.add(sparkleshare);\r
- }\r
}\r
\r
- final ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(repositoryUrls);\r
+ final String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());\r
ListDataProvider<GitClientApplication> displayedAppsDp = new ListDataProvider<GitClientApplication>(displayedApps);\r
DataView<GitClientApplication> appMenus = new DataView<GitClientApplication>("appMenus", displayedAppsDp) {\r
private static final long serialVersionUID = 1L;\r
public void populateItem(final Item<GitClientApplication> item) {\r
final GitClientApplication clientApp = item.getModelObject();\r
\r
+ // filter the urls for the client app\r
+ List<RepositoryUrl> urls;\r
+ if (clientApp.minimumPermission == null) {\r
+ // client app does not specify minimum access permission\r
+ urls = repositoryUrls;\r
+ } else {\r
+ urls = new ArrayList<RepositoryUrl>();\r
+ for (RepositoryUrl repoUrl : repositoryUrls) {\r
+ if (repoUrl.permission == null) {\r
+ // external permissions, assume it is satisfactory\r
+ urls.add(repoUrl);\r
+ } else if (repoUrl.permission.atLeast(clientApp.minimumPermission)) {\r
+ // repo url meets minimum permission requirement\r
+ urls.add(repoUrl);\r
+ }\r
+ }\r
+ }\r
+ \r
+ if (urls.size() == 0) {\r
+ // do not show this app menu because there are no urls\r
+ item.add(new Label("appMenu").setVisible(false));\r
+ return;\r
+ }\r
+ \r
+ Fragment appMenu = new Fragment("appMenu", "appMenuFragment", this);\r
+ appMenu.setRenderBodyOnly(true);\r
+ item.add(appMenu);\r
+ \r
// menu button\r
- item.add(new Label("applicationName", clientApp.name));\r
+ appMenu.add(new Label("applicationName", clientApp.name));\r
\r
// application icon\r
Component img;\r
if (StringUtils.isEmpty(clientApp.icon)) {\r
img = WicketUtils.newClearPixel("applicationIcon").setVisible(false); \r
} else {\r
- img = WicketUtils.newImage("applicationIcon", clientApp.icon); \r
+ if (clientApp.icon.contains("://")) {\r
+ // external image\r
+ img = new ExternalImage("applicationIcon", clientApp.icon);\r
+ } else {\r
+ // context image\r
+ img = WicketUtils.newImage("applicationIcon", clientApp.icon);\r
+ }\r
} \r
- item.add(img);\r
+ appMenu.add(img);\r
\r
// application menu title, may be a link\r
if (StringUtils.isEmpty(clientApp.productUrl)) {\r
- item.add(new Label("applicationTitle", clientApp.toString()));\r
+ appMenu.add(new Label("applicationTitle", clientApp.toString()));\r
} else {\r
- item.add(new LinkPanel("applicationTitle", null, clientApp.toString(), clientApp.productUrl, true));\r
+ appMenu.add(new LinkPanel("applicationTitle", null, clientApp.toString(), clientApp.productUrl, true));\r
}\r
\r
// brief application description\r
if (StringUtils.isEmpty(clientApp.description)) {\r
- item.add(new Label("applicationDescription").setVisible(false));\r
+ appMenu.add(new Label("applicationDescription").setVisible(false));\r
} else {\r
- item.add(new Label("applicationDescription", clientApp.description));\r
+ appMenu.add(new Label("applicationDescription", clientApp.description));\r
}\r
\r
// brief application legal info, copyright, license, etc\r
if (StringUtils.isEmpty(clientApp.legal)) {\r
- item.add(new Label("applicationLegal").setVisible(false));\r
+ appMenu.add(new Label("applicationLegal").setVisible(false));\r
} else {\r
- item.add(new Label("applicationLegal", clientApp.legal));\r
+ appMenu.add(new Label("applicationLegal", clientApp.legal));\r
}\r
\r
// a nested repeater for all action items\r
+ ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(urls);\r
DataView<RepositoryUrl> actionItems = new DataView<RepositoryUrl>("actionItems", urlsDp) {\r
private static final long serialVersionUID = 1L;\r
\r
public void populateItem(final Item<RepositoryUrl> repoLinkItem) {\r
RepositoryUrl repoUrl = repoLinkItem.getModelObject();\r
- \r
Fragment fragment = new Fragment("actionItem", "actionFragment", this);\r
fragment.add(createPermissionBadge("permission", repoUrl));\r
\r
if (!StringUtils.isEmpty(clientApp.cloneUrl)) {\r
// custom registered url\r
- String url = MessageFormat.format(clientApp.cloneUrl, repoUrl);\r
+ String url = substitute(clientApp.cloneUrl, repoUrl.url, baseURL);\r
fragment.add(new LinkPanel("content", "applicationMenuItem", getString("gb.clone") + " " + repoUrl.url, url));\r
repoLinkItem.add(fragment);\r
fragment.add(new Label("copyFunction").setVisible(false));\r
} else if (!StringUtils.isEmpty(clientApp.command)) {\r
// command-line\r
- String command = MessageFormat.format(clientApp.command, repoUrl);\r
+ String command = substitute(clientApp.command, repoUrl.url, baseURL);\r
Label content = new Label("content", command);\r
WicketUtils.setCssClass(content, "commandMenuItem");\r
fragment.add(content);\r
fragment.add(createCopyFragment(command));\r
}\r
}};\r
- item.add(actionItems);\r
+ appMenu.add(actionItems);\r
}\r
};\r
\r
return applicationMenus;\r
}\r
\r
- protected GitClientApplication getSparkleShareAppMenu(UserModel user, RepositoryModel repository) {\r
- String url = null;\r
- if (repository.isBare && repository.isSparkleshared()) {\r
- String username = null;\r
- if (UserModel.ANONYMOUS != user) {\r
- username = user.username;\r
- }\r
- if (isGitblitServingRepositories()) {\r
- // Gitblit as server\r
- // ensure user can rewind\r
- if (user.canRewindRef(repository)) {\r
- String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());\r
- url = SparkleShareInviteServlet.asLink(baseURL, repository.name, username);\r
- }\r
- } else {\r
- // Gitblit as viewer, assume RW+ permission\r
- String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());\r
- url = SparkleShareInviteServlet.asLink(baseURL, repository.name, username);\r
- }\r
- }\r
-\r
- // sparkleshare invite url\r
- if (!StringUtils.isEmpty(url)) {\r
- GitClientApplication app = new GitClientApplication();\r
- app.name = "SparkleShare";\r
- app.title = "SparkleShare\u2122";\r
- app.description = "an open source collaboration and sharing tool";\r
- app.legal = "released under the GPLv3 open source license";\r
- app.cloneUrl = url;\r
- app.platforms = new String [] { "windows", "macintosh", "linux" };\r
- app.productUrl = "http://sparkleshare.org";\r
- app.icon = "sparkleshare_32x32.png";\r
- app.isActive = true;\r
- return app;\r
- }\r
- return null;\r
+ protected String substitute(String pattern, String repoUrl, String baseUrl) {\r
+ return pattern.replace("${repoUrl}", repoUrl).replace("${baseUrl}", baseUrl);\r
}\r
\r
protected boolean isGitblitServingRepositories() {\r