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