diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main/distrib/data/gitblit.properties | 14 | ||||
-rw-r--r-- | src/main/java/com/gitblit/manager/ProjectManager.java | 15 | ||||
-rw-r--r-- | src/main/java/com/gitblit/manager/RepositoryManager.java | 100 | ||||
-rw-r--r-- | src/main/java/com/gitblit/servlet/RawServlet.java | 12 | ||||
-rw-r--r-- | src/main/java/com/gitblit/tickets/TicketIndexer.java | 2 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/BlobPage.java | 5 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html | 8 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/pages/RepositoryPage.java | 16 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/panels/BasePanel.java | 11 | ||||
-rw-r--r-- | src/main/java/com/gitblit/wicket/panels/HistoryPanel.java | 6 | ||||
-rw-r--r-- | src/site/rpc.mkd | 2 | ||||
-rw-r--r-- | src/site/siteindex.mkd | 2 |
12 files changed, 141 insertions, 52 deletions
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties index 65fe41e5..d8ddc285 100644 --- a/src/main/distrib/data/gitblit.properties +++ b/src/main/distrib/data/gitblit.properties @@ -844,8 +844,18 @@ realm.minPasswordLength = 5 # SINCE 0.5.0
web.siteName =
-# The canonical url of your Gitblit server to bs used in email notifications.
-# e.g. web.canonicalUrl = https://demo-gitblit.rhcloud.com
+# The canonical url of your Gitblit server to be used in repository url generation,
+# RSS feeds, and all embedded links in email and plugin-based notifications.
+#
+# If you are running Gitblit on a non-standard http port (i.e. not 80 and not 443)
+# then you must specify that port in this url otherwise your generated urls will be
+# incorrect.
+#
+# The hostname of this url will be extracted for SSH and GIT protocol repository
+# url generation.
+#
+# e.g. web.canonicalUrl = https://dev.gitblit.com
+# web.canonicalUrl = https://dev.gitblit.com:8443
#
# SINCE 1.4.0
web.canonicalUrl =
diff --git a/src/main/java/com/gitblit/manager/ProjectManager.java b/src/main/java/com/gitblit/manager/ProjectManager.java index b30f4f17..666f5210 100644 --- a/src/main/java/com/gitblit/manager/ProjectManager.java +++ b/src/main/java/com/gitblit/manager/ProjectManager.java @@ -178,19 +178,20 @@ public class ProjectManager implements IProjectManager { map.put("", configs.get("")); for (RepositoryModel model : repositoryManager.getRepositoryModels(user)) { - String rootPath = StringUtils.getRootPath(model.name).toLowerCase(); - if (!map.containsKey(rootPath)) { + String projectPath = StringUtils.getRootPath(model.name); + String projectKey = projectPath.toLowerCase(); + if (!map.containsKey(projectKey)) { ProjectModel project; - if (configs.containsKey(rootPath)) { + if (configs.containsKey(projectKey)) { // clone the project model because it's repository list will // be tailored for the requesting user - project = DeepCopier.copy(configs.get(rootPath)); + project = DeepCopier.copy(configs.get(projectKey)); } else { - project = new ProjectModel(rootPath); + project = new ProjectModel(projectPath); } - map.put(rootPath, project); + map.put(projectKey, project); } - map.get(rootPath).addRepository(model); + map.get(projectKey).addRepository(model); } // sort projects, root project first diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java index e0721c7c..0160363e 100644 --- a/src/main/java/com/gitblit/manager/RepositoryManager.java +++ b/src/main/java/com/gitblit/manager/RepositoryManager.java @@ -422,11 +422,12 @@ public class RepositoryManager implements IRepositoryManager { @Override public void addToCachedRepositoryList(RepositoryModel model) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { - repositoryListCache.put(model.name.toLowerCase(), model); + String key = getRepositoryKey(model.name); + repositoryListCache.put(key, model); // update the fork origin repository with this repository clone if (!StringUtils.isEmpty(model.originRepository)) { - String originKey = model.originRepository.toLowerCase(); + String originKey = getRepositoryKey(model.originRepository); if (repositoryListCache.containsKey(originKey)) { RepositoryModel origin = repositoryListCache.get(originKey); origin.addFork(model.name); @@ -445,7 +446,8 @@ public class RepositoryManager implements IRepositoryManager { if (StringUtils.isEmpty(name)) { return null; } - return repositoryListCache.remove(name.toLowerCase()); + String key = getRepositoryKey(name); + return repositoryListCache.remove(key); } /** @@ -554,7 +556,7 @@ public class RepositoryManager implements IRepositoryManager { // rebuild fork networks for (RepositoryModel model : repositoryListCache.values()) { if (!StringUtils.isEmpty(model.originRepository)) { - String originKey = model.originRepository.toLowerCase(); + String originKey = getRepositoryKey(model.originRepository); if (repositoryListCache.containsKey(originKey)) { RepositoryModel origin = repositoryListCache.get(originKey); origin.addFork(model.name); @@ -590,15 +592,13 @@ public class RepositoryManager implements IRepositoryManager { /** * Returns the JGit repository for the specified name. * - * @param repositoryName + * @param name * @param logError * @return repository or null */ @Override - public Repository getRepository(String repositoryName, boolean logError) { - // Decode url-encoded repository name (issue-278) - // http://stackoverflow.com/questions/17183110 - repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~"); + public Repository getRepository(String name, boolean logError) { + String repositoryName = fixRepositoryName(name); if (isCollectingGarbage(repositoryName)) { logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName)); @@ -680,16 +680,14 @@ public class RepositoryManager implements IRepositoryManager { * Returns the repository model for the specified repository. This method * does not consider user access permissions. * - * @param repositoryName + * @param name * @return repository model or null */ @Override - public RepositoryModel getRepositoryModel(String repositoryName) { - // Decode url-encoded repository name (issue-278) - // http://stackoverflow.com/questions/17183110 - repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~"); + public RepositoryModel getRepositoryModel(String name) { + String repositoryName = fixRepositoryName(name); - String repositoryKey = repositoryName.toLowerCase(); + String repositoryKey = getRepositoryKey(repositoryName); if (!repositoryListCache.containsKey(repositoryKey)) { RepositoryModel model = loadRepositoryModel(repositoryName); if (model == null) { @@ -702,7 +700,7 @@ public class RepositoryManager implements IRepositoryManager { // cached model RepositoryModel model = repositoryListCache.get(repositoryKey); - if (gcExecutor.isCollectingGarbage(model.name)) { + if (isCollectingGarbage(model.name)) { // Gitblit is busy collecting garbage, use our cached model RepositoryModel rm = DeepCopier.copy(model); rm.isCollectingGarbage = true; @@ -758,6 +756,52 @@ public class RepositoryManager implements IRepositoryManager { } /** + * Replaces illegal character patterns in a repository name. + * + * @param repositoryName + * @return a corrected name + */ + private String fixRepositoryName(String repositoryName) { + if (StringUtils.isEmpty(repositoryName)) { + return repositoryName; + } + + // Decode url-encoded repository name (issue-278) + // http://stackoverflow.com/questions/17183110 + String name = repositoryName.replace("%7E", "~").replace("%7e", "~"); + name = name.replace("%2F", "/").replace("%2f", "/"); + + if (name.charAt(name.length() - 1) == '/') { + name = name.substring(0, name.length() - 1); + } + + // strip duplicate-slashes from requests for repositoryName (ticket-117, issue-454) + // specify first char as slash so we strip leading slashes + char lastChar = '/'; + StringBuilder sb = new StringBuilder(); + for (char c : name.toCharArray()) { + if (c == '/' && lastChar == c) { + continue; + } + sb.append(c); + lastChar = c; + } + + return sb.toString(); + } + + /** + * Returns the cache key for the repository name. + * + * @param repositoryName + * @return the cache key for the repository + */ + private String getRepositoryKey(String repositoryName) { + String name = fixRepositoryName(repositoryName); + return StringUtils.stripDotGit(name).toLowerCase(); + } + + /** * Workaround JGit. I need to access the raw config object directly in order * to see if the config is dirty so that I can reload a repository model. * If I use the stock JGit method to get the config it already reloads the @@ -932,7 +976,8 @@ public class RepositoryManager implements IRepositoryManager { if (!caseSensitiveCheck && settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // if we are caching use the cache to determine availability // otherwise we end up adding a phantom repository to the cache - return repositoryListCache.containsKey(repositoryName.toLowerCase()); + String key = getRepositoryKey(repositoryName); + return repositoryListCache.containsKey(key); } Repository r = getRepository(repositoryName, false); if (r == null) { @@ -970,7 +1015,7 @@ public class RepositoryManager implements IRepositoryManager { } String userProject = ModelUtils.getPersonalPath(username); if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { - String originKey = origin.toLowerCase(); + String originKey = getRepositoryKey(origin); String userPath = userProject + "/"; // collect all origin nodes in fork network @@ -987,7 +1032,7 @@ public class RepositoryManager implements IRepositoryManager { } if (originModel.originRepository != null) { - String ooKey = originModel.originRepository.toLowerCase(); + String ooKey = getRepositoryKey(originModel.originRepository); roots.add(ooKey); originModel = repositoryListCache.get(ooKey); } else { @@ -1000,7 +1045,8 @@ public class RepositoryManager implements IRepositoryManager { if (repository.startsWith(userPath)) { RepositoryModel model = repositoryListCache.get(repository); if (!StringUtils.isEmpty(model.originRepository)) { - if (roots.contains(model.originRepository.toLowerCase())) { + String ooKey = getRepositoryKey(model.originRepository); + if (roots.contains(ooKey)) { // user has a fork in this graph return model.name; } @@ -1038,9 +1084,11 @@ public class RepositoryManager implements IRepositoryManager { public ForkModel getForkNetwork(String repository) { if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) { // find the root, cached - RepositoryModel model = repositoryListCache.get(repository.toLowerCase()); + String key = getRepositoryKey(repository); + RepositoryModel model = repositoryListCache.get(key); while (model.originRepository != null) { - model = repositoryListCache.get(model.originRepository.toLowerCase()); + String originKey = getRepositoryKey(model.originRepository); + model = repositoryListCache.get(originKey); } ForkModel root = getForkModelFromCache(model.name); return root; @@ -1056,7 +1104,8 @@ public class RepositoryManager implements IRepositoryManager { } private ForkModel getForkModelFromCache(String repository) { - RepositoryModel model = repositoryListCache.get(repository.toLowerCase()); + String key = getRepositoryKey(repository); + RepositoryModel model = repositoryListCache.get(key); if (model == null) { return null; } @@ -1287,7 +1336,7 @@ public class RepositoryManager implements IRepositoryManager { @Override public void updateRepositoryModel(String repositoryName, RepositoryModel repository, boolean isCreate) throws GitBlitException { - if (gcExecutor.isCollectingGarbage(repositoryName)) { + if (isCollectingGarbage(repositoryName)) { throw new GitBlitException(MessageFormat.format("sorry, Gitblit is busy collecting garbage in {0}", repositoryName)); } @@ -1371,7 +1420,8 @@ public class RepositoryManager implements IRepositoryManager { // update this repository's origin's fork list if (!StringUtils.isEmpty(repository.originRepository)) { - RepositoryModel origin = repositoryListCache.get(repository.originRepository.toLowerCase()); + String originKey = getRepositoryKey(repository.originRepository); + RepositoryModel origin = repositoryListCache.get(originKey); if (origin != null && !ArrayUtils.isEmpty(origin.forks)) { origin.forks.remove(repositoryName); origin.forks.add(repository.name); diff --git a/src/main/java/com/gitblit/servlet/RawServlet.java b/src/main/java/com/gitblit/servlet/RawServlet.java index ff3f7359..a9e58202 100644 --- a/src/main/java/com/gitblit/servlet/RawServlet.java +++ b/src/main/java/com/gitblit/servlet/RawServlet.java @@ -173,6 +173,9 @@ public class RawServlet extends DaggerServlet { repository = path.substring(0, slash); } offset += slash; + if (offset == 0) { + offset++; + } r = repositoryManager.getRepository(repository, false); if (repository.equals(path)) { // either only repository in url or no repository found @@ -252,6 +255,15 @@ public class RawServlet extends DaggerServlet { // load, interpret, and serve text content as UTF-8 String [] encodings = runtimeManager.getSettings().getStrings(Keys.web.blobEncodings).toArray(new String[0]); String content = JGitUtils.getStringContent(r, commit.getTree(), requestedPath, encodings); + if (content == null) { + logger.error("RawServlet Failed to load {} {} {}", repository, commit.getName(), path); + String str = MessageFormat.format( + "# Error\nSorry, the requested resource **{0}** was not found.", + requestedPath); + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + error(response, str); + return; + } byte [] bytes = content.getBytes(Constants.ENCODING); response.setContentLength(bytes.length); diff --git a/src/main/java/com/gitblit/tickets/TicketIndexer.java b/src/main/java/com/gitblit/tickets/TicketIndexer.java index 3929a000..98fe6977 100644 --- a/src/main/java/com/gitblit/tickets/TicketIndexer.java +++ b/src/main/java/com/gitblit/tickets/TicketIndexer.java @@ -143,7 +143,7 @@ public class TicketIndexer { private String escape(String value) { if (value.charAt(0) != '"') { - if (value.indexOf('/') > -1) { + if (value.indexOf('/') > -1 || value.indexOf('-') > -1) { return "\"" + value + "\""; } } diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java index f3d0bc92..0938fcde 100644 --- a/src/main/java/com/gitblit/wicket/pages/BlobPage.java +++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java @@ -21,6 +21,7 @@ import java.util.Map; 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.image.Image;
import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -52,6 +53,10 @@ public class BlobPage extends RepositoryPage { final String blobPath = WicketUtils.getPath(params);
String [] encodings = getEncodings();
+ if (StringUtils.isEmpty(objectId) && StringUtils.isEmpty(blobPath)) {
+ throw new RedirectException(TreePage.class, WicketUtils.newRepositoryParameter(repositoryName));
+ }
+
if (StringUtils.isEmpty(blobPath)) {
// blob by objectid
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html index 0ecb54d5..cd777b4b 100644 --- a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html +++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage_ko.html @@ -30,7 +30,7 @@ <div class="span8 offset1"> <h2><center>Git 배우기</center></h2> <p>만약 사용법에 자신이 없다면, Git 사용법을 더 잘 이해하기 위해 - <a href="http://book.git-scm.com/ko">Git Community Book</a> 을 볼 것을 고려해 보세요.</p> + <a href="http://git-scm.com/book/ko">Pro Git</a> 을 볼 것을 고려해 보세요.</p> <h4>오픈 소스 Git 클라이언트</h4> <table> @@ -43,11 +43,11 @@ </tbody> </table> - <h4>유료/클로즈드 소스 Git 클라이언트</h4> + <h4>유료/비공개 소스 Git 클라이언트</h4> <table> <tbody> <tr><td><a href="http://www.syntevo.com/smartgithg">SmartGit/Hg</a></td><td>자바 어플리케이션 (명령어 기반 공식 Git 필요)</td></tr> - <tr><td><a href="http://www.sourcetreeapp.com/">SourceTree</a></td><td>윈도와 맥에서 가능한 Git 과 Mercurial용 무료 클라이언트</td></tr> + <tr><td><a href="http://www.sourcetreeapp.com/">SourceTree</a></td><td>윈도와 맥에서 사용 가능한 Git 과 Mercurial용 무료 클라이언트</td></tr> <tr><td><a href="http://www.git-tower.com/">Tower</a></td><td>맥 OS X 용 Git 클라이언트</td></tr> </tbody> </table> @@ -58,4 +58,4 @@ </div> </wicket:extend> </body> -</html>
\ No newline at end of file +</html> diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java index fcf659af..253c4fe4 100644 --- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java +++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java @@ -166,10 +166,10 @@ public abstract class RepositoryPage extends RootPage { add(navigationPanel);
add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
- .getRelativePathPrefixToContextRoot(), repositoryName, null, 0)));
+ .getRelativePathPrefixToContextRoot(), getRepositoryName(), null, 0)));
// add floating search form
- SearchForm searchForm = new SearchForm("searchForm", repositoryName);
+ SearchForm searchForm = new SearchForm("searchForm", getRepositoryName());
add(searchForm);
searchForm.setTranslatedAttributes();
@@ -193,7 +193,7 @@ public abstract class RepositoryPage extends RootPage { private List<NavLink> registerNavLinks() {
PageParameters params = null;
if (!StringUtils.isEmpty(repositoryName)) {
- params = WicketUtils.newRepositoryParameter(repositoryName);
+ params = WicketUtils.newRepositoryParameter(getRepositoryName());
}
List<NavLink> navLinks = new ArrayList<NavLink>();
@@ -216,7 +216,7 @@ public abstract class RepositoryPage extends RootPage { navLinks.add(new PageNavLink("gb.commits", LogPage.class, params));
navLinks.add(new PageNavLink("gb.tree", TreePage.class, params));
if (app().tickets().isReady() && (app().tickets().isAcceptingNewTickets(model) || app().tickets().hasTickets(model))) {
- PageParameters tParams = WicketUtils.newOpenTicketsParameter(repositoryName);
+ PageParameters tParams = WicketUtils.newOpenTicketsParameter(getRepositoryName());
navLinks.add(new PageNavLink("gb.tickets", TicketsPage.class, tParams)); } navLinks.add(new PageNavLink("gb.docs", DocsPage.class, params, true));
@@ -229,7 +229,7 @@ public abstract class RepositoryPage extends RootPage { // per-repository extra navlinks
if (JGitUtils.getPagesBranch(r) != null) {
ExternalNavLink pagesLink = new ExternalNavLink("gb.pages", PagesServlet.asLink(
- getRequest().getRelativePathPrefixToContextRoot(), repositoryName, null), true);
+ getRequest().getRelativePathPrefixToContextRoot(), getRepositoryName(), null), true);
navLinks.add(pagesLink);
}
@@ -422,6 +422,10 @@ public abstract class RepositoryPage extends RootPage { return m;
}
+ protected String getRepositoryName() {
+ return getRepositoryModel().name;
+ }
+
protected RevCommit getCommit() {
RevCommit commit = JGitUtils.getCommit(r, objectId);
if (commit == null) {
@@ -630,7 +634,7 @@ public abstract class RepositoryPage extends RootPage { r = null;
}
// setup page header and footer
- setupPage(repositoryName, "/ " + getPageName());
+ setupPage(getRepositoryName(), "/ " + getPageName());
super.onBeforeRender();
}
diff --git a/src/main/java/com/gitblit/wicket/panels/BasePanel.java b/src/main/java/com/gitblit/wicket/panels/BasePanel.java index e8f8f6f2..73f8e471 100644 --- a/src/main/java/com/gitblit/wicket/panels/BasePanel.java +++ b/src/main/java/com/gitblit/wicket/panels/BasePanel.java @@ -22,6 +22,8 @@ import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.model.Model;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Keys;
@@ -36,6 +38,8 @@ public abstract class BasePanel extends Panel { private transient TimeUtils timeUtils;
+ private transient Logger logger;
+
public BasePanel(String wicketId) {
super(wicketId);
}
@@ -44,6 +48,13 @@ public abstract class BasePanel extends Panel { return GitBlitWebApp.get();
}
+ protected Logger logger() {
+ if (logger == null) {
+ logger = LoggerFactory.getLogger(getClass());
+ }
+ return logger;
+ }
+
protected String getContextUrl() {
return getRequest().getRelativePathPrefixToContextRoot();
}
diff --git a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java index 21f38388..e1706a09 100644 --- a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java +++ b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java @@ -38,8 +38,6 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import com.gitblit.Constants;
import com.gitblit.Keys;
@@ -63,8 +61,6 @@ public class HistoryPanel extends BasePanel { private static final long serialVersionUID = 1L;
- private final Logger log = LoggerFactory.getLogger(getClass());
-
private boolean hasMore;
public HistoryPanel(String wicketId, final String repositoryName, final String objectId,
@@ -84,7 +80,7 @@ public class HistoryPanel extends BasePanel { // commit missing
String msg = MessageFormat.format("Failed to find history of **{0}** *{1}*",
path, objectId);
- log.error(msg + " " + repositoryName);
+ logger().error(msg + " " + repositoryName);
add(new Label("commitHeader", MarkdownUtils.transformMarkdown(msg)).setEscapeModelStrings(false));
add(new Label("breadcrumbs"));
} else {
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd index 302084fb..de4ed471 100644 --- a/src/site/rpc.mkd +++ b/src/site/rpc.mkd @@ -14,7 +14,7 @@ The Gitblit JSON RPC mechanism, like the Gitblit JGit servlet, syndication/feed ### Gitblit Manager
-[Gitblit Manager](http://code.google.com/p/gitblit/downloads/detail?name=%MANAGER%) is an example Java/Swing application that allows remote management (repository and user objects) and administration (server settings) of a Gitblit server.
+The Gitblit Manager is an example Java/Swing application that allows remote management (repository and user objects) and administration (server settings) of a Gitblit server.
This application uses a combination of RSS feeds and the JSON RPC interface, both of which are part of the [Gitblit API](http://code.google.com/p/gitblit/downloads/detail?name=%API%) library, to present live information from a Gitblit server. Some JSON RPC methods from the utility class `com.gitblit.utils.RpcUtils` are not currently used by the Gitblit Manager.
diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd index 58e18af1..aae25c89 100644 --- a/src/site/siteindex.mkd +++ b/src/site/siteindex.mkd @@ -83,7 +83,7 @@ Administrators can create and manage all repositories, user accounts, and teams - Custom authentication, authorization, and user management
- Rich RSS feeds
- JSON-based RPC mechanism
-- [Java Client RSS/JSON API library](http://code.google.com/p/gitblit/downloads/detail?name=%API%) for custom integration
+- Java Client RSS/JSON API library for custom integration
### Backup Strategy
|