From ed552ba47c02779c270ffd62841d6d1048dade70 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Sun, 22 Nov 2015 14:37:16 -0500
Subject: [PATCH] Merge branch 'develop'

---
 src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html                    |   20 
 src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java             |   12 
 src/test/java/com/gitblit/tests/GravatarTest.java                                 |   74 
 src/main/java/com/gitblit/models/TicketModel.java                                 |  124 
 src/main/java/.gitignore                                                          |    1 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.java                               |   50 
 src/main/java/com/gitblit/wicket/panels/ReflogPanel.java                          |    2 
 src/main/distrib/data/gitblit.properties                                          | 1892 -----
 src/main/java/com/gitblit/wicket/pages/LogoutPage.html                            |    2 
 src/main/java/com/gitblit/utils/CompressionUtils.java                             |    4 
 src/main/java/com/gitblit/wicket/pages/RootPage.java                              |   35 
 src/main/java/com/gitblit/wicket/panels/LogPanel.java                             |    2 
 src/main/java/com/gitblit/wicket/pages/EditTicketPage.java                        |   30 
 src/main/resources/blink32.png                                                    |    0 
 src/main/java/com/gitblit/models/FilestoreModel.java                              |  159 
 src/main/java/com/gitblit/wicket/TicketsUI.java                                   |   59 
 src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java        |    6 
 src/main/java/com/gitblit/transport/ssh/WelcomeShell.java                         |   11 
 src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java                      |   34 
 src/main/java/com/gitblit/tickets/viewTicket.html                                 |   12 
 src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java                       |    2 
 src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html                        |   69 
 src/main/java/com/gitblit/wicket/pages/ProjectsPage.java                          |    2 
 src/main/java/com/gitblit/servlet/RpcFilter.java                                  |   22 
 src/main/java/com/gitblit/transport/ssh/NullKeyManager.java                       |    3 
 src/site/plugins_extensions.mkd                                                   |   10 
 src/main/resources/sub32.png                                                      |    0 
 src/main/java/com/gitblit/tickets/ITicketService.java                             |    5 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties                      |   10 
 src/main/java/com/gitblit/wicket/pages/ReflogPage.java                            |   17 
 src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html                          |    2 
 src/main/java/com/gitblit/wicket/pages/TicketsPage.java                           |   16 
 src/main/java/com/gitblit/wicket/panels/AvatarImage.java                          |   23 
 src/main/resources/octicons/octicons.ttf                                          |    0 
 src/main/java/com/gitblit/utils/ActivityUtils.java                                |    4 
 src/main/java/com/gitblit/wicket/pages/EditUserPage.java                          |   34 
 src/main/java/com/gitblit/wicket/pages/NewTicketPage.html                         |    6 
 src/main/java/com/gitblit/guice/WorkQueueProvider.java                            |   57 
 src/main/java/com/gitblit/utils/JGitUtils.java                                    |  539 
 src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java                    |   10 
 src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java                    |   72 
 src/main/java/com/gitblit/manager/ServicesManager.java                            |  267 
 src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html                    |   13 
 src/main/java/com/gitblit/wicket/panels/DigestsPanel.java                         |    6 
 src/main/java/com/gitblit/wicket/pages/SessionPage.java                           |   73 
 src/test/java/com/gitblit/tests/StringUtilsTest.java                              |    2 
 src/main/java/com/gitblit/servlet/GitblitContext.java                             |   89 
 src/test/java/com/gitblit/tests/SshDaemonTest.java                                |    4 
 src/test/java/com/gitblit/tests/LdapAuthenticationTest.java                       |   12 
 src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java                  |   34 
 src/main/java/com/gitblit/wicket/pages/BlobPage.java                              |    5 
 src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java              |    2 
 src/main/java/login_zh_TW.mkd                                                     |    3 
 src/main/distrib/linux/install-service-fedora.sh                                  |    7 
 src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java                    |    2 
 src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java                         |  707 +
 src/main/java/com/gitblit/tickets/TicketNotifier.java                             |    7 
 src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java                      |  170 
 src/site/siteindex.mkd                                                            |    5 
 src/main/java/com/gitblit/wicket/pages/RootPage.html                              |    2 
 src/main/java/com/gitblit/manager/UserManager.java                                |   10 
 src/main/java/com/gitblit/servlet/PagesFilter.java                                |   17 
 src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java                    |   87 
 src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java                     |    4 
 src/site/upgrade_go.mkd                                                           |   14 
 src/main/java/com/gitblit/wicket/pages/BlamePage.java                             |    3 
 src/main/java/com/gitblit/auth/PAMAuthProvider.java                               |   12 
 src/main/java/com/gitblit/servlet/FederationServlet.java                          |   22 
 src/main/java/com/gitblit/guice/WebModule.java                                    |  126 
 src/main/java/com/gitblit/transport/ssh/SshServerSession.java                     |    4 
 src/main/java/com/gitblit/manager/ProjectManager.java                             |    4 
 src/main/java/com/gitblit/transport/ssh/FileKeyManager.java                       |    2 
 src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm |    8 
 src/site/setup_authentication.mkd                                                 |   10 
 src/main/java/com/gitblit/auth/LdapAuthProvider.java                              |   31 
 src/main/java/com/gitblit/manager/IFilestoreManager.java                          |   54 
 src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java                    |    6 
 src/main/java/com/gitblit/manager/IServicesManager.java                           |   94 
 src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java                 |   46 
 src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html                      |    1 
 src/main/resources/octicons/octicons-local.ttf                                    |    0 
 src/main/java/com/gitblit/wicket/pages/TicketPage.java                            |   30 
 src/main/java/com/gitblit/wicket/pages/UserPage.java                              |   15 
 src/test/java/com/gitblit/tests/FilestoreServletTest.java                         |  355 +
 src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties                   |  772 ++
 src/main/java/com/gitblit/wicket/pages/ComparePage.html                           |    2 
 src/main/resources/octicons/octicons.css                                          |  235 
 src/main/java/com/gitblit/manager/AuthenticationManager.java                      |  175 
 src/main/java/com/gitblit/servlet/RawServlet.java                                 |   59 
 src/main/distrib/data/defaults.properties                                         | 2046 ++++++
 src/main/java/com/gitblit/ConfigUserService.java                                  |   33 
 src/main/java/com/gitblit/wicket/panels/ActivityPanel.java                        |    2 
 src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java                  |   76 
 src/main/java/com/gitblit/wicket/pages/ProjectsPage.html                          |    2 
 .gitignore                                                                        |    3 
 src/main/java/com/gitblit/utils/cli/CmdLineParser.java                            |   10 
 src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java                          |   12 
 src/main/java/com/gitblit/wicket/pages/EditTeamPage.java                          |   18 
 src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java                          |   25 
 src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html                         |   30 
 src/main/java/com/gitblit/wicket/pages/NewTicketPage.java                         |   27 
 src/main/resources/octicons/octicons.svg                                          |  198 
 src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java                      |   71 
 src/main/java/com/gitblit/service/MailService.java                                |    4 
 src/main/java/com/gitblit/wicket/panels/AvatarImage.html                          |    0 
 src/main/java/com/gitblit/wicket/pages/TicketPage.html                            |    3 
 src/main/java/com/gitblit/wicket/panels/TeamsPanel.java                           |    1 
 src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java            |   35 
 src/main/resources/gitblit.css                                                    |  374 
 src/main/java/com/gitblit/servlet/GitServlet.java                                 |   18 
 src/main/java/com/gitblit/servlet/RawFilter.java                                  |   24 
 src/main/java/com/gitblit/wicket/pages/TreePage.java                              |    2 
 src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java                     |    3 
 src/main/java/com/gitblit/utils/MetricUtils.java                                  |    2 
 src/main/java/com/gitblit/servlet/BranchGraphServlet.java                         |   25 
 src/main/java/com/gitblit/auth/SalesforceAuthProvider.java                        |   13 
 src/main/java/com/gitblit/utils/JSoupXssFilter.java                               |    5 
 src/main/java/com/gitblit/tickets/TicketIndexer.java                              |    9 
 src/main/java/com/gitblit/guice/ITicketServiceProvider.java                       |   73 
 src/main/java/com/gitblit/wicket/pages/ReflogPage.html                            |    4 
 build.xml                                                                         |   79 
 src/main/java/com/gitblit/tickets/RedisTicketService.java                         |    8 
 src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java                      |    2 
 src/main/java/com/gitblit/utils/StringUtils.java                                  |   17 
 src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java                      |   26 
 src/main/java/com/gitblit/servlet/RobotsTxtServlet.java                           |   15 
 src/main/java/com/gitblit/wicket/pages/ForksPage.java                             |    7 
 src/test/java/com/gitblit/tests/GitBlitTest.java                                  |    2 
 src/test/java/com/gitblit/tests/JGitUtilsTest.java                                |    9 
 src/main/java/com/gitblit/auth/AuthenticationProvider.java                        |   30 
 src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java           |    2 
 src/main/java/welcome_zh_TW.mkd                                                   |    3 
 src/test/java/com/gitblit/tests/AuthenticationManagerTest.java                    |  679 +
 src/main/java/com/gitblit/models/RepositoryUrl.java                               |    4 
 src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js                         |  263 
 src/test/java/com/gitblit/tests/FilestoreManagerTest.java                         |  547 +
 src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java                    |    2 
 src/main/java/com/gitblit/servlet/RpcServlet.java                                 |   13 
 src/main/java/com/gitblit/tickets/QueryResult.java                                |    4 
 src/main/java/com/gitblit/wicket/pages/BasePage.html                              |    2 
 src/main/java/com/gitblit/utils/X509Utils.java                                    |   32 
 src/main/java/com/gitblit/tickets/NullTicketService.java                          |    5 
 src/site/setup_war.mkd                                                            |    5 
 src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java                           |    2 
 src/main/java/com/gitblit/servlet/SyndicationServlet.java                         |   26 
 src/main/java/com/gitblit/wicket/pages/FilestorePage.java                         |  104 
 src/main/java/com/gitblit/servlet/AuthenticationFilter.java                       |  374 
 src/main/java/com/gitblit/wicket/pages/CommitPage.java                            |    4 
 gitblit.iml                                                                       |  336 
 src/main/java/com/gitblit/wicket/pages/ComparePage.java                           |   36 
 src/main/java/com/gitblit/wicket/panels/SearchPanel.java                          |    2 
 NOTICE                                                                            |    9 
 src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java              |   42 
 src/main/java/WEB-INF/web.xml                                                     |  411 -
 src/test/java/com/gitblit/tests/GitblitUnitTest.java                              |    5 
 src/main/java/com/gitblit/AvatarGenerator.java                                    |   22 
 src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java                         |  771 +
 src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java                    |   72 
 src/main/java/com/gitblit/GravatarGenerator.java                                  |   34 
 src/main/java/com/gitblit/auth/WindowsAuthProvider.java                           |   12 
 src/main/java/com/gitblit/servlet/SyndicationFilter.java                          |   24 
 src/main/.gitignore                                                               |    1 
 src/main/java/com/gitblit/servlet/FilestoreServlet.java                           |  493 +
 src/test/java/com/gitblit/tests/DiffUtilsTest.java                                |    7 
 src/main/java/com/gitblit/wicket/panels/TeamsPanel.html                           |    6 
 src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java                  |  154 
 src/main/java/com/gitblit/wicket/panels/HistoryPanel.java                         |    4 
 src/main/java/com/gitblit/wicket/GitblitWicketFilter.java                         |   28 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java                   |    2 
 src/main/java/com/gitblit/servlet/ProxyFilter.java                                |   33 
 src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java                        |   29 
 src/main/java/com/gitblit/wicket/panels/TicketListPanel.java                      |   12 
 src/main/java/com/gitblit/servlet/GitFilter.java                                  |  104 
 src/test/java/com/gitblit/tests/TicketServiceTest.java                            |   44 
 src/main/java/com/gitblit/FederationClient.java                                   |  384 
 src/site/setup_fail2ban.mkd                                                       |   24 
 src/main/java/com/gitblit/tickets/BranchTicketService.java                        |   19 
 src/main/java/com/gitblit/FileSettings.java                                       |   67 
 src/main/java/com/gitblit/servlet/DownloadZipFilter.java                          |   24 
 src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java                  |   16 
 src/main/java/com/gitblit/service/LuceneService.java                              |   11 
 src/main/java/com/gitblit/servlet/DownloadZipServlet.java                         |   17 
 src/main/java/com/gitblit/transport/ssh/SshKey.java                               |    7 
 src/main/java/com/gitblit/wicket/pages/FilestorePage.html                         |   37 
 src/main/java/com/gitblit/manager/IRuntimeManager.java                            |   39 
 .classpath                                                                        |   96 
 src/main/java/com/gitblit/Constants.java                                          |   39 
 src/main/java/com/gitblit/servlet/LogoServlet.java                                |   15 
 src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java             |   18 
 src/main/java/com/gitblit/wicket/pages/BasePage.java                              |   40 
 src/main/java/com/gitblit/wicket/pages/TagPage.java                               |    4 
 src/test/java/com/gitblit/tests/GitBlitSuite.java                                 |   15 
 src/site/tickets_overview.mkd                                                     |    2 
 src/main/java/com/gitblit/manager/IGitblit.java                                   |   18 
 src/main/java/com/gitblit/manager/PluginManager.java                              |  112 
 src/main/resources/octicons/octicons.less                                         |  233 
 src/main/java/com/gitblit/manager/FederationManager.java                          |    4 
 src/main/java/com/gitblit/wicket/WicketUtils.java                                 |   37 
 src/main/java/com/gitblit/wicket/pages/EditTicketPage.html                        |    6 
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.html                        |   35 
 src/main/java/com/gitblit/servlet/JsonServlet.java                                |    4 
 src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java                        |   25 
 src/main/java/com/gitblit/manager/NotificationManager.java                        |    4 
 src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java                  |    7 
 src/main/java/com/gitblit/wicket/FilestoreUI.java                                 |   62 
 src/main/java/com/gitblit/auth/RedmineAuthProvider.java                           |   14 
 src/main/java/com/gitblit/utils/HtmlBuilder.java                                  |   96 
 src/main/java/com/gitblit/git/PatchsetReceivePack.java                            |    4 
 src/main/java/com/gitblit/utils/RefLogUtils.java                                  |    8 
 src/main/java/com/gitblit/utils/JsonUtils.java                                    |   21 
 src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java                    |    3 
 src/main/java/com/gitblit/GitBlit.java                                            |  451 -
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties                         |   31 
 src/main/resources/octicons/octicons.eot                                          |    0 
 src/main/resources/octicons/octicons.woff                                         |    0 
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java               |   38 
 src/test/java/com/gitblit/tests/UITicketTest.java                                 |  151 
 src/main/java/com/gitblit/wicket/GitblitWicketApp.java                            |    6 
 src/main/java/com/gitblit/transport/ssh/SshDaemon.java                            |   29 
 src/main/java/com/gitblit/manager/FilestoreManager.java                           |  441 +
 releases.moxie                                                                    |  103 
 src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java                   |   48 
 src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java                |   79 
 src/test/java/com/gitblit/tests/SshUnitTest.java                                  |    6 
 src/main/java/com/gitblit/utils/PathUtils.java                                    |   92 
 src/main/java/com/gitblit/wicket/pages/TicketsPage.html                           |   19 
 src/site/setup_go.mkd                                                             |    9 
 src/main/java/com/gitblit/manager/RuntimeManager.java                             |   59 
 src/main/java/com/gitblit/models/RefModel.java                                    |    7 
 src/site/setup_bugtraq.mkd                                                        |    2 
 src/main/java/com/gitblit/GitBlitServer.java                                      |   52 
 src/site/design.mkd                                                               |    1 
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html               |   31 
 src/main/java/com/gitblit/manager/GitblitManager.java                             |  273 
 src/main/resources/octicons/sprockets-octicons.scss                               |  230 
 src/main/java/com/gitblit/manager/RepositoryManager.java                          |   55 
 src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java                  |   43 
 src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties                      |   10 
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.java                        |   25 
 src/main/java/com/gitblit/manager/IAuthenticationManager.java                     |   32 
 src/main/java/com/gitblit/servlet/AccessDeniedServlet.java                        |   63 
 src/main/java/com/gitblit/tickets/FileTicketService.java                          |    9 
 src/site/upgrade_war.mkd                                                          |   23 
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java                   |   40 
 src/main/java/com/gitblit/servlet/PagesServlet.java                               |  219 
 src/main/java/com/gitblit/wicket/pages/BlobPage.html                              |    5 
 src/main/java/com/gitblit/servlet/PtServlet.java                                  |   15 
 /dev/null                                                                         |    8 
 src/test/java/com/gitblit/tests/PathUtilsTest.java                                |   66 
 src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java                |   34 
 src/main/java/com/gitblit/wicket/panels/TicketListPanel.html                      |    3 
 src/site/rpc.mkd                                                                  |   10 
 src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html                        |    2 
 src/main/java/com/gitblit/utils/MarkdownUtils.java                                |    3 
 src/main/java/com/gitblit/wicket/panels/BranchesPanel.java                        |   28 
 src/main/java/com/gitblit/utils/DiffUtils.java                                    |  171 
 build.moxie                                                                       |   76 
 src/main/java/com/gitblit/guice/CoreModule.java                                   |   85 
 258 files changed, 14,485 insertions(+), 5,412 deletions(-)

diff --git a/.classpath b/.classpath
index 2644d44..ccf6a4e 100644
--- a/.classpath
+++ b/.classpath
@@ -5,59 +5,61 @@
 	<classpathentry kind="src" path="src/test/java" output="bin/test-classes" />
 	<classpathentry kind="src" path="src/test/bugtraq" output="bin/test-classes" />
 	<classpathentry kind="src" path="src/main/resources" />
-	<classpathentry kind="lib" path="ext/dagger-1.1.0.jar" sourcepath="ext/src/dagger-1.1.0.jar" />
+	<classpathentry kind="lib" path="ext/guice-4.0.jar" sourcepath="ext/src/guice-4.0.jar" />
 	<classpathentry kind="lib" path="ext/javax.inject-1.jar" sourcepath="ext/src/javax.inject-1.jar" />
-	<classpathentry kind="lib" path="ext/dagger-compiler-1.1.0.jar" sourcepath="ext/src/dagger-compiler-1.1.0.jar" />
-	<classpathentry kind="lib" path="ext/javawriter-2.1.1.jar" sourcepath="ext/src/javawriter-2.1.1.jar" />
+	<classpathentry kind="lib" path="ext/aopalliance-1.0.jar" sourcepath="ext/src/aopalliance-1.0.jar" />
+	<classpathentry kind="lib" path="ext/guava-18.0.jar" sourcepath="ext/src/guava-18.0.jar" />
+	<classpathentry kind="lib" path="ext/guice-servlet-4.0-gb2.jar" sourcepath="ext/src/guice-servlet-4.0-gb2.jar" />
 	<classpathentry kind="lib" path="ext/annotations-12.0.jar" sourcepath="ext/src/annotations-12.0.jar" />
 	<classpathentry kind="lib" path="ext/log4j-1.2.17.jar" sourcepath="ext/src/log4j-1.2.17.jar" />
-	<classpathentry kind="lib" path="ext/slf4j-api-1.7.5.jar" sourcepath="ext/src/slf4j-api-1.7.5.jar" />
-	<classpathentry kind="lib" path="ext/slf4j-log4j12-1.7.5.jar" sourcepath="ext/src/slf4j-log4j12-1.7.5.jar" />
+	<classpathentry kind="lib" path="ext/slf4j-api-1.7.12.jar" sourcepath="ext/src/slf4j-api-1.7.12.jar" />
+	<classpathentry kind="lib" path="ext/slf4j-log4j12-1.7.12.jar" sourcepath="ext/src/slf4j-log4j12-1.7.12.jar" />
 	<classpathentry kind="lib" path="ext/javax.mail-1.5.1.jar" sourcepath="ext/src/javax.mail-1.5.1.jar" />
 	<classpathentry kind="lib" path="ext/javax.servlet-api-3.1.0.jar" sourcepath="ext/src/javax.servlet-api-3.1.0.jar" />
-	<classpathentry kind="lib" path="ext/jetty-all-9.2.3.v20140905.jar" sourcepath="ext/src/jetty-all-9.2.3.v20140905.jar" />
-	<classpathentry kind="lib" path="ext/wicket-1.4.21.jar" sourcepath="ext/src/wicket-1.4.21.jar" />
-	<classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.21.jar" sourcepath="ext/src/wicket-auth-roles-1.4.21.jar" />
-	<classpathentry kind="lib" path="ext/wicket-extensions-1.4.21.jar" sourcepath="ext/src/wicket-extensions-1.4.21.jar" />
-	<classpathentry kind="lib" path="ext/lucene-core-4.6.0.jar" sourcepath="ext/src/lucene-core-4.6.0.jar" />
-	<classpathentry kind="lib" path="ext/lucene-analyzers-common-4.6.0.jar" sourcepath="ext/src/lucene-analyzers-common-4.6.0.jar" />
-	<classpathentry kind="lib" path="ext/lucene-highlighter-4.6.0.jar" sourcepath="ext/src/lucene-highlighter-4.6.0.jar" />
-	<classpathentry kind="lib" path="ext/lucene-memory-4.6.0.jar" sourcepath="ext/src/lucene-memory-4.6.0.jar" />
-	<classpathentry kind="lib" path="ext/lucene-queries-4.6.0.jar" sourcepath="ext/src/lucene-queries-4.6.0.jar" />
-	<classpathentry kind="lib" path="ext/lucene-queryparser-4.6.0.jar" sourcepath="ext/src/lucene-queryparser-4.6.0.jar" />
-	<classpathentry kind="lib" path="ext/lucene-sandbox-4.6.0.jar" sourcepath="ext/src/lucene-sandbox-4.6.0.jar" />
+	<classpathentry kind="lib" path="ext/jetty-all-9.2.13.v20150730.jar" sourcepath="ext/src/jetty-all-9.2.13.v20150730.jar" />
+	<classpathentry kind="lib" path="ext/wicket-1.4.22.jar" sourcepath="ext/src/wicket-1.4.22.jar" />
+	<classpathentry kind="lib" path="ext/wicket-auth-roles-1.4.22.jar" sourcepath="ext/src/wicket-auth-roles-1.4.22.jar" />
+	<classpathentry kind="lib" path="ext/wicket-extensions-1.4.22.jar" sourcepath="ext/src/wicket-extensions-1.4.22.jar" />
+	<classpathentry kind="lib" path="ext/lucene-core-4.10.4.jar" sourcepath="ext/src/lucene-core-4.10.4.jar" />
+	<classpathentry kind="lib" path="ext/lucene-analyzers-common-4.10.4.jar" sourcepath="ext/src/lucene-analyzers-common-4.10.4.jar" />
+	<classpathentry kind="lib" path="ext/lucene-highlighter-4.10.4.jar" sourcepath="ext/src/lucene-highlighter-4.10.4.jar" />
+	<classpathentry kind="lib" path="ext/lucene-memory-4.10.4.jar" sourcepath="ext/src/lucene-memory-4.10.4.jar" />
+	<classpathentry kind="lib" path="ext/lucene-queries-4.10.4.jar" sourcepath="ext/src/lucene-queries-4.10.4.jar" />
+	<classpathentry kind="lib" path="ext/lucene-queryparser-4.10.4.jar" sourcepath="ext/src/lucene-queryparser-4.10.4.jar" />
+	<classpathentry kind="lib" path="ext/lucene-sandbox-4.10.4.jar" sourcepath="ext/src/lucene-sandbox-4.10.4.jar" />
 	<classpathentry kind="lib" path="ext/jakarta-regexp-1.4.jar" />
-	<classpathentry kind="lib" path="ext/pegdown-1.4.2.jar" sourcepath="ext/src/pegdown-1.4.2.jar" />
-	<classpathentry kind="lib" path="ext/parboiled-java-1.1.6.jar" sourcepath="ext/src/parboiled-java-1.1.6.jar" />
-	<classpathentry kind="lib" path="ext/parboiled-core-1.1.6.jar" sourcepath="ext/src/parboiled-core-1.1.6.jar" />
-	<classpathentry kind="lib" path="ext/asm-4.1.jar" sourcepath="ext/src/asm-4.1.jar" />
-	<classpathentry kind="lib" path="ext/asm-tree-4.1.jar" sourcepath="ext/src/asm-tree-4.1.jar" />
-	<classpathentry kind="lib" path="ext/asm-analysis-4.1.jar" sourcepath="ext/src/asm-analysis-4.1.jar" />
-	<classpathentry kind="lib" path="ext/asm-util-4.1.jar" sourcepath="ext/src/asm-util-4.1.jar" />
+	<classpathentry kind="lib" path="ext/pegdown-1.5.0.jar" sourcepath="ext/src/pegdown-1.5.0.jar" />
+	<classpathentry kind="lib" path="ext/parboiled-java-1.1.7.jar" sourcepath="ext/src/parboiled-java-1.1.7.jar" />
+	<classpathentry kind="lib" path="ext/parboiled-core-1.1.7.jar" sourcepath="ext/src/parboiled-core-1.1.7.jar" />
+	<classpathentry kind="lib" path="ext/asm-5.0.3.jar" sourcepath="ext/src/asm-5.0.3.jar" />
+	<classpathentry kind="lib" path="ext/asm-tree-5.0.3.jar" sourcepath="ext/src/asm-tree-5.0.3.jar" />
+	<classpathentry kind="lib" path="ext/asm-analysis-5.0.3.jar" sourcepath="ext/src/asm-analysis-5.0.3.jar" />
+	<classpathentry kind="lib" path="ext/asm-util-5.0.3.jar" sourcepath="ext/src/asm-util-5.0.3.jar" />
 	<classpathentry kind="lib" path="ext/wikitext-core-1.4.jar" sourcepath="ext/src/wikitext-core-1.4.jar" />
 	<classpathentry kind="lib" path="ext/twiki-core-1.4.jar" sourcepath="ext/src/twiki-core-1.4.jar" />
 	<classpathentry kind="lib" path="ext/textile-core-1.4.jar" sourcepath="ext/src/textile-core-1.4.jar" />
 	<classpathentry kind="lib" path="ext/tracwiki-core-1.4.jar" sourcepath="ext/src/tracwiki-core-1.4.jar" />
 	<classpathentry kind="lib" path="ext/mediawiki-core-1.4.jar" sourcepath="ext/src/mediawiki-core-1.4.jar" />
 	<classpathentry kind="lib" path="ext/confluence-core-1.4.jar" sourcepath="ext/src/confluence-core-1.4.jar" />
-	<classpathentry kind="lib" path="ext/org.eclipse.jgit-3.5.1.201410131835-r.jar" sourcepath="ext/src/org.eclipse.jgit-3.5.1.201410131835-r.jar" />
-	<classpathentry kind="lib" path="ext/jsch-0.1.50.jar" sourcepath="ext/src/jsch-0.1.50.jar" />
+	<classpathentry kind="lib" path="ext/org.eclipse.jgit-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit-4.1.1.201511131810-r.jar" />
+	<classpathentry kind="lib" path="ext/jsch-0.1.53.jar" sourcepath="ext/src/jsch-0.1.53.jar" />
 	<classpathentry kind="lib" path="ext/JavaEWAH-0.7.9.jar" sourcepath="ext/src/JavaEWAH-0.7.9.jar" />
-	<classpathentry kind="lib" path="ext/httpclient-4.1.3.jar" sourcepath="ext/src/httpclient-4.1.3.jar" />
-	<classpathentry kind="lib" path="ext/httpcore-4.1.4.jar" sourcepath="ext/src/httpcore-4.1.4.jar" />
-	<classpathentry kind="lib" path="ext/commons-logging-1.1.1.jar" sourcepath="ext/src/commons-logging-1.1.1.jar" />
+	<classpathentry kind="lib" path="ext/httpclient-4.3.6.jar" sourcepath="ext/src/httpclient-4.3.6.jar" />
+	<classpathentry kind="lib" path="ext/httpcore-4.3.3.jar" sourcepath="ext/src/httpcore-4.3.3.jar" />
+	<classpathentry kind="lib" path="ext/commons-logging-1.1.3.jar" sourcepath="ext/src/commons-logging-1.1.3.jar" />
 	<classpathentry kind="lib" path="ext/commons-codec-1.7.jar" sourcepath="ext/src/commons-codec-1.7.jar" />
-	<classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar" />
-	<classpathentry kind="lib" path="ext/bcprov-jdk15on-1.49.jar" sourcepath="ext/src/bcprov-jdk15on-1.49.jar" />
-	<classpathentry kind="lib" path="ext/bcmail-jdk15on-1.49.jar" sourcepath="ext/src/bcmail-jdk15on-1.49.jar" />
-	<classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.49.jar" sourcepath="ext/src/bcpkix-jdk15on-1.49.jar" />
-	<classpathentry kind="lib" path="ext/sshd-core-0.12.0.jar" sourcepath="ext/src/sshd-core-0.12.0.jar" />
-	<classpathentry kind="lib" path="ext/mina-core-2.0.7.jar" sourcepath="ext/src/mina-core-2.0.7.jar" />
+	<classpathentry kind="lib" path="ext/org.eclipse.jdt.annotation-1.1.0.jar" />
+	<classpathentry kind="lib" path="ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" sourcepath="ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar" />
+	<classpathentry kind="lib" path="ext/bcprov-jdk15on-1.52.jar" sourcepath="ext/src/bcprov-jdk15on-1.52.jar" />
+	<classpathentry kind="lib" path="ext/bcmail-jdk15on-1.52.jar" sourcepath="ext/src/bcmail-jdk15on-1.52.jar" />
+	<classpathentry kind="lib" path="ext/bcpkix-jdk15on-1.52.jar" sourcepath="ext/src/bcpkix-jdk15on-1.52.jar" />
+	<classpathentry kind="lib" path="ext/sshd-core-1.0.0.jar" sourcepath="ext/src/sshd-core-1.0.0.jar" />
+	<classpathentry kind="lib" path="ext/mina-core-2.0.9.jar" sourcepath="ext/src/mina-core-2.0.9.jar" />
 	<classpathentry kind="lib" path="ext/rome-0.9.jar" sourcepath="ext/src/rome-0.9.jar" />
 	<classpathentry kind="lib" path="ext/jdom-1.0.jar" sourcepath="ext/src/jdom-1.0.jar" />
-	<classpathentry kind="lib" path="ext/gson-1.7.2.jar" sourcepath="ext/src/gson-1.7.2.jar" />
-	<classpathentry kind="lib" path="ext/groovy-all-1.8.8.jar" sourcepath="ext/src/groovy-all-1.8.8.jar" />
-	<classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.0.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.0.jar" />
+	<classpathentry kind="lib" path="ext/gson-2.3.1.jar" sourcepath="ext/src/gson-2.3.1.jar" />
+	<classpathentry kind="lib" path="ext/groovy-all-2.4.4.jar" sourcepath="ext/src/groovy-all-2.4.4.jar" />
+	<classpathentry kind="lib" path="ext/unboundid-ldapsdk-2.3.8.jar" sourcepath="ext/src/unboundid-ldapsdk-2.3.8.jar" />
 	<classpathentry kind="lib" path="ext/ivy-2.2.0.jar" sourcepath="ext/src/ivy-2.2.0.jar" />
 	<classpathentry kind="lib" path="ext/jcalendar-1.3.2.jar" />
 	<classpathentry kind="lib" path="ext/commons-compress-1.4.1.jar" sourcepath="ext/src/commons-compress-1.4.1.jar" />
@@ -66,16 +68,15 @@
 	<classpathentry kind="lib" path="ext/force-partner-api-24.0.0.jar" sourcepath="ext/src/force-partner-api-24.0.0.jar" />
 	<classpathentry kind="lib" path="ext/force-wsc-24.0.0.jar" sourcepath="ext/src/force-wsc-24.0.0.jar" />
 	<classpathentry kind="lib" path="ext/js-1.7R2.jar" sourcepath="ext/src/js-1.7R2.jar" />
-	<classpathentry kind="lib" path="ext/freemarker-2.3.19.jar" sourcepath="ext/src/freemarker-2.3.19.jar" />
-	<classpathentry kind="lib" path="ext/waffle-jna-1.5.jar" sourcepath="ext/src/waffle-jna-1.5.jar" />
-	<classpathentry kind="lib" path="ext/platform-3.5.0.jar" sourcepath="ext/src/platform-3.5.0.jar" />
-	<classpathentry kind="lib" path="ext/jna-3.5.0.jar" sourcepath="ext/src/jna-3.5.0.jar" />
-	<classpathentry kind="lib" path="ext/guava-13.0.1.jar" sourcepath="ext/src/guava-13.0.1.jar" />
-	<classpathentry kind="lib" path="ext/libpam4j-1.7.jar" sourcepath="ext/src/libpam4j-1.7.jar" />
-	<classpathentry kind="lib" path="ext/args4j-2.0.26.jar" sourcepath="ext/src/args4j-2.0.26.jar" />
-	<classpathentry kind="lib" path="ext/jedis-2.3.1.jar" sourcepath="ext/src/jedis-2.3.1.jar" />
+	<classpathentry kind="lib" path="ext/freemarker-2.3.22.jar" sourcepath="ext/src/freemarker-2.3.22.jar" />
+	<classpathentry kind="lib" path="ext/waffle-jna-1.7.3.jar" sourcepath="ext/src/waffle-jna-1.7.3.jar" />
+	<classpathentry kind="lib" path="ext/jna-4.1.0.jar" sourcepath="ext/src/jna-4.1.0.jar" />
+	<classpathentry kind="lib" path="ext/jna-platform-4.1.0.jar" sourcepath="ext/src/jna-platform-4.1.0.jar" />
+	<classpathentry kind="lib" path="ext/libpam4j-1.8.jar" sourcepath="ext/src/libpam4j-1.8.jar" />
+	<classpathentry kind="lib" path="ext/args4j-2.0.29.jar" sourcepath="ext/src/args4j-2.0.29.jar" />
+	<classpathentry kind="lib" path="ext/jedis-2.6.2.jar" sourcepath="ext/src/jedis-2.6.2.jar" />
 	<classpathentry kind="lib" path="ext/commons-pool2-2.0.jar" sourcepath="ext/src/commons-pool2-2.0.jar" />
-	<classpathentry kind="lib" path="ext/pf4j-0.8.0.jar" sourcepath="ext/src/pf4j-0.8.0.jar" />
+	<classpathentry kind="lib" path="ext/pf4j-0.9.0.jar" sourcepath="ext/src/pf4j-0.9.0.jar" />
 	<classpathentry kind="lib" path="ext/tika-core-1.5.jar" sourcepath="ext/src/tika-core-1.5.jar" />
 	<classpathentry kind="lib" path="ext/jsoup-1.7.3.jar" sourcepath="ext/src/jsoup-1.7.3.jar" />
 	<classpathentry kind="lib" path="ext/junit-4.11.jar" sourcepath="ext/src/junit-4.11.jar" />
@@ -88,6 +89,9 @@
 	<classpathentry kind="lib" path="ext/json-20080701.jar" sourcepath="ext/src/json-20080701.jar" />
 	<classpathentry kind="lib" path="ext/selenium-api-2.28.0.jar" sourcepath="ext/src/selenium-api-2.28.0.jar" />
 	<classpathentry kind="lib" path="ext/commons-exec-1.1.jar" sourcepath="ext/src/commons-exec-1.1.jar" />
+	<classpathentry kind="lib" path="ext/platform-3.4.0.jar" sourcepath="ext/src/platform-3.4.0.jar" />
+	<classpathentry kind="lib" path="ext/mockito-core-1.10.19.jar" sourcepath="ext/src/mockito-core-1.10.19.jar" />
+	<classpathentry kind="lib" path="ext/objenesis-2.1.jar" sourcepath="ext/src/objenesis-2.1.jar" />
 	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER" />
 	<classpathentry kind="src" path="src/main/dagger">
 		<attributes>
diff --git a/.gitignore b/.gitignore
index 6c262bb..e268ccb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,11 @@
+tags
 /temp
 /lib
 /ext
 /build
 /site
 /git
+/lucene
 /build.properties
 /federation.properties
 /mailtest.properties
@@ -27,3 +29,4 @@
 /**/.idea
 /**/init.lua
 /**/session
+/nbproject/private
diff --git a/NOTICE b/NOTICE
index da61b20..69d7c74 100644
--- a/NOTICE
+++ b/NOTICE
@@ -358,3 +358,12 @@
    Apache License 2.0
 
    https://github.com/decebals/pf4j
+
+---------------------------------------------------------------------------
+google-guice
+---------------------------------------------------------------------------
+   google-guice, release under the 
+   Apache License 2.0
+
+   https://code.google.com/p/google-guice
+   
\ No newline at end of file
diff --git a/build.moxie b/build.moxie
index 8dcb975..e5f7c73 100644
--- a/build.moxie
+++ b/build.moxie
@@ -3,14 +3,14 @@
 #
 
 # Specify minimum Moxie version required to build
-requires: 0.9.3
+requires: 0.9.4
 
 # Project Metadata
 name: Gitblit
 description: pure Java Git solution
 groupId: com.gitblit
 artifactId: gitblit
-version: 1.6.3-SNAPSHOT
+version: 1.7.0-SNAPSHOT
 inceptionYear: 2011
 
 # Current stable release
@@ -19,7 +19,7 @@
 
 # Project urls
 url: 'http://gitblit.com'
-issuesUrl: 'http://code.google.com/p/gitblit/issues/list'
+issuesUrl: 'https://github.com/gitblit/gitblit'
 socialNetworkUrl: 'https://plus.google.com/114464678392593421684'
 forumUrl: 'http://groups.google.com/group/gitblit'
 mavenUrl: 'http://gitblit.github.io/gitblit-maven'
@@ -58,7 +58,7 @@
 sourceDirectories:
 - compile 'src/main/java'
 - compile 'src/main/bugtraq'
-- compile 'src/main/dagger' apt
+- compile 'src/main/gen' apt
 - test 'src/test/java'
 - test 'src/test/bugtraq'
 # Moxie supports one site-scoped directory for mx:doc
@@ -95,23 +95,27 @@
 registeredRepositories:
 - { id: eclipse, url: 'http://repo.eclipse.org/content/groups/releases' }
 - { id: eclipse-snapshots, url: 'http://repo.eclipse.org/content/groups/snapshots' }
-- { id: atlassian-contrib, url: 'https://maven.atlassian.com/content/repositories/atlassian-3rdparty' }
+- { id: gitblit, url: 'http://gitblit.github.io/gitblit-maven' }
 
 # Source all dependencies from the following repositories in the specified order
-repositories: central, eclipse-snapshots, eclipse, atlassian-contrib
+repositories: central, eclipse-snapshots, eclipse, gitblit
 
 # Convenience properties for dependencies
 properties: {
-  jetty.version  : 9.2.9.v20150224
-  wicket.version : 1.4.21
-  lucene.version : 4.6.0
-  jgit.version   : 3.5.1.201410131835-r
-  groovy.version : 1.8.8
-  bouncycastle.version : 1.49
+  jetty.version  : 9.2.13.v20150730
+  slf4j.version  : 1.7.12
+  wicket.version : 1.4.22
+  lucene.version : 4.10.4
+  jgit.version   : 4.1.1.201511131810-r
+  groovy.version : 2.4.4
+  bouncycastle.version : 1.52
   selenium.version : 2.28.0
   wikitext.version : 1.4
-  sshd.version: 0.12.0
-  mina.version: 2.0.7
+  sshd.version: 1.0.0
+  mina.version: 2.0.9
+  guice.version : 4.0
+  # Gitblit maintains a fork of guice-servlet
+  guice-servlet.version : 4.0-gb2
   }
 
 # Dependencies
@@ -126,15 +130,14 @@
 #
 
 dependencies:
-# Dagger dependency injection library (annotation processor)
-- compile 'com.squareup.dagger:dagger:1.1.0' :war apt
-- compile 'com.squareup.dagger:dagger-compiler:1.1.0' :war optional apt
-# Standard dependencies
+- compile 'com.google.inject:guice:${guice.version}' :war :fedclient
+- compile 'com.google.inject.extensions:guice-servlet:${guice-servlet.version}' :war
+- compile 'com.google.guava:guava:18.0' :war :fedclient
 - compile 'com.intellij:annotations:12.0' :war
-- compile 'log4j:log4j:1.2.17' :war :fedclient :authority
-- compile 'org.slf4j:slf4j-api:1.7.5' :war :fedclient :authority
-- compile 'org.slf4j:slf4j-log4j12:1.7.5' :war :fedclient :authority
-- compile 'com.sun.mail:javax.mail:1.5.1' :war :authority
+- compile 'log4j:log4j:1.2.17' :war :fedclient :manager
+- compile 'org.slf4j:slf4j-api:${slf4j.version}' :war :fedclient :manager
+- compile 'org.slf4j:slf4j-log4j12:${slf4j.version}' :war :fedclient :manager
+- compile 'com.sun.mail:javax.mail:1.5.1' :war
 - compile 'javax.servlet:javax.servlet-api:3.1.0' :fedclient
 - compile 'org.eclipse.jetty.aggregate:jetty-all:${jetty.version}' @jar
 - compile 'org.apache.wicket:wicket:${wicket.version}' :war !org.mockito
@@ -145,36 +148,36 @@
 - compile 'org.apache.lucene:lucene-highlighter:${lucene.version}' :war :fedclient
 - compile 'org.apache.lucene:lucene-memory:${lucene.version}' :war :fedclient
 - compile 'org.apache.lucene:lucene-queryparser:${lucene.version}' :war :fedclient
-- compile 'org.pegdown:pegdown:1.4.2' :war
+- compile 'org.pegdown:pegdown:1.5.0' :war
 - compile 'org.fusesource.wikitext:wikitext-core:${wikitext.version}' :war
 - compile 'org.fusesource.wikitext:twiki-core:${wikitext.version}' :war
 - compile 'org.fusesource.wikitext:textile-core:${wikitext.version}' :war
 - compile 'org.fusesource.wikitext:tracwiki-core:${wikitext.version}' :war
 - compile 'org.fusesource.wikitext:mediawiki-core:${wikitext.version}' :war
 - compile 'org.fusesource.wikitext:confluence-core:${wikitext.version}' :war
-- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager :authority !junit
-- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :manager :authority !junit
-- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war :authority
-- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war :authority
-- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war :authority
+- compile 'org.eclipse.jgit:org.eclipse.jgit:${jgit.version}' :war :fedclient :manager !junit
+- compile 'org.eclipse.jgit:org.eclipse.jgit.http.server:${jgit.version}' :war :manager !junit
+- compile 'org.bouncycastle:bcprov-jdk15on:${bouncycastle.version}' :war
+- compile 'org.bouncycastle:bcmail-jdk15on:${bouncycastle.version}' :war
+- compile 'org.bouncycastle:bcpkix-jdk15on:${bouncycastle.version}' :war
 - compile 'org.apache.sshd:sshd-core:${sshd.version}' :war !org.easymock
 - compile 'org.apache.mina:mina-core:${mina.version}' :war !org.easymock
 - compile 'rome:rome:0.9' :war :manager :api
-- compile 'com.google.code.gson:gson:1.7.2' :war :fedclient :manager :api
+- compile 'com.google.code.gson:gson:2.3.1' :war :fedclient :manager :api
 - compile 'org.codehaus.groovy:groovy-all:${groovy.version}' :war
-- compile 'com.unboundid:unboundid-ldapsdk:2.3.0' :war
+- compile 'com.unboundid:unboundid-ldapsdk:2.3.8' :war
 - compile 'org.apache.ivy:ivy:2.2.0' :war
 - compile 'com.toedter:jcalendar:1.3.2' :authority
 - compile 'org.apache.commons:commons-compress:1.4.1' :war
 - compile 'commons-io:commons-io:2.2' :war
 - compile 'com.force.api:force-partner-api:24.0.0' :war
-- compile 'org.freemarker:freemarker:2.3.19' :war
-- compile 'com.github.dblock.waffle:waffle-jna:1.5' :war
-- compile 'org.kohsuke:libpam4j:1.7' :war
-- compile 'args4j:args4j:2.0.26' :war :fedclient :authority
+- compile 'org.freemarker:freemarker:2.3.22' :war
+- compile 'com.github.dblock.waffle:waffle-jna:1.7.3' :war
+- compile 'org.kohsuke:libpam4j:1.8' :war
+- compile 'args4j:args4j:2.0.29' :war :fedclient
 - compile 'commons-codec:commons-codec:1.7' :war
-- compile 'redis.clients:jedis:2.3.1' :war
-- compile 'ro.fortsoft.pf4j:pf4j:0.8.0' :war
+- compile 'redis.clients:jedis:2.6.2' :war
+- compile 'ro.fortsoft.pf4j:pf4j:0.9.0' :war
 - compile 'org.apache.tika:tika-core:1.5' :war
 - compile 'org.jsoup:jsoup:1.7.3' :war
 - test 'junit'
@@ -182,6 +185,7 @@
 - test 'org.seleniumhq.selenium:selenium-java:${selenium.version}' @jar
 - test 'org.seleniumhq.selenium:selenium-support:${selenium.version}' @jar
 - test 'org.seleniumhq.selenium:selenium-firefox-driver:${selenium.version}'
+- test 'org.mockito:mockito-core:1.10.19'
 # Dependencies with the "build" scope are retrieved
 # and injected into the Ant runtime classpath
 - build 'jacoco'
diff --git a/build.xml b/build.xml
index 99c2966..c7dc499 100644
--- a/build.xml
+++ b/build.xml
@@ -8,7 +8,7 @@
 		documentation @ http://gitblit.github.io/moxie
 		~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 	-->
-	<property name="moxie.version" value="0.9.3" />
+	<property name="moxie.version" value="0.9.4" />
 	<property name="moxie.url" value="http://gitblit.github.io/moxie/maven" />
 	<property name="moxie.jar" value="moxie-toolkit-${moxie.version}.jar" />
 	<property name="moxie.dir" value="${user.home}/.moxie" />
@@ -41,9 +41,10 @@
 		<mx:init verbose="no" mxroot="${moxie.dir}" />
 				
 		<!-- Set Ant project properties -->
-		<property name="distribution.zipfile" value="gitblit-${project.version}.zip" />
-		<property name="distribution.tgzfile" value="gitblit-${project.version}.tar.gz" />
-		<property name="distribution.warfile" value="gitblit-${project.version}.war" />
+		<property name="release.name" value="gitblit-${project.version}"/>
+		<property name="distribution.zipfile" value="${release.name}.zip" />
+		<property name="distribution.tgzfile" value="${release.name}.tar.gz" />
+		<property name="distribution.warfile" value="${release.name}.war" />
 		<property name="fedclient.zipfile" value="fedclient-${project.version}.zip" />
 		<property name="manager.zipfile" value="manager-${project.version}.zip" />
 		<property name="authority.zipfile" value="authority-${project.version}.zip" />
@@ -81,10 +82,9 @@
 			<fileset dir="${project.distrib.dir}/data" />
 		</copy>
 		
-		<!-- copy gitblit.properties to the source directory.
-		     this file is only used for parsing setting descriptions. -->
-		<copy tofile="${project.src.dir}/reference.properties" overwrite="true"
-			file="${project.distrib.dir}/data/gitblit.properties" />
+		<!-- copy defaults.properties to the source directory -->
+		<copy tofile="${project.src.dir}/defaults.properties" overwrite="true"
+			file="${project.distrib.dir}/data/defaults.properties" />
 
 		<!-- copy clientapps.json to the source directory.
 		     this file is only used if a local file is not provided. -->
@@ -101,8 +101,8 @@
 	-->
 	<target name="compile" depends="setup" description="compiles Gitblit from source">
 		
-		<!-- Generate the Keys class from the properties file -->
-		<mx:keys propertiesfile="${project.distrib.dir}/data/gitblit.properties"
+		<!-- Generate the Keys class from the defaults.properties file -->
+		<mx:keys propertiesfile="${project.distrib.dir}/data/defaults.properties"
 			 	outputclass="com.gitblit.Keys"
 			 	todir="${project.src.dir}" />
 
@@ -170,14 +170,17 @@
 		
 		<echo>Building Gitblit GO ${project.version}</echo>
 
-		<local name="go.dir" />
-		<property name="go.dir" value="${project.outputDirectory}/go" />	
+		<local name="go.dir"/>
+		<property name="go.dir" value="${project.outputDirectory}/go"/>
 		<delete dir="${go.dir}" />
+		
+		<local name="go.release.dir" />
+		<property name="go.release.dir" value="${go.dir}/${release.name}" />	
 
 		<local name="webinf" />
 		<property name="webinf" value="${project.compileOutputDirectory}/WEB-INF" />
 
-		<prepareDataDirectory toDir="${go.dir}/data" />
+		<prepareDataDirectory toDir="${go.release.dir}/data" />
 		
 		<!-- Copy the web.xml from the prototype web.xml -->
 		<copy todir="${webinf}" overwrite="true">
@@ -188,42 +191,43 @@
 		</copy>
 		
 		<!-- Build jar -->
-		<mx:jar destfile="${go.dir}/gitblit.jar" includeresources="true">
+		<mx:jar destfile="${go.release.dir}/gitblit.jar" includeresources="true">
 			<mainclass name="com.gitblit.GitBlitServer" />
 			<launcher paths="ext" />
 		</mx:jar>
 
 		<!-- Generate the docs for the GO build -->
-		<generateDocs toDir="${go.dir}/docs" />
-		
+		<generateDocs toDir="${go.release.dir}/docs" />
+
 		<!-- Create GO Windows Zip deployment -->
 		<mx:zip basedir="${go.dir}">
 			<!-- LICENSE and NOTICE -->
-			<fileset dir="${basedir}" >
+			<zipfileset dir="${basedir}" prefix="${release.name}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
-			</fileset>
+			</zipfileset>
 			<!-- Windows distrib files -->
-			<zipfileset dir="${project.distrib.dir}/win" />
+			<zipfileset dir="${project.distrib.dir}/win" prefix="${release.name}"/>
 			<!-- Gitblit Authority data -->
-			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="${release.name}/data/certs" />
+
 			<!-- include all dependencies -->
-			<dependencies prefix="ext" />
+			<dependencies prefix="${release.name}/ext" />
 		</mx:zip>
 
 		<!-- Create GO Linux/OSX tar.gz deployment -->
 		<mx:tar basedir="${go.dir}" longfile="gnu" compression="gzip">
 			<!-- LICENSE and NOTICE -->
-			<fileset dir="${basedir}" >
+			<zipfileset dir="${basedir}" prefix="${release.name}">
 				<include name="LICENSE" />
 				<include name="NOTICE" />
-			</fileset>
+			</zipfileset>
 			<!-- Linux/OSX distrib files -->
-			<tarfileset dir="${project.distrib.dir}/linux" filemode="755" />
+			<tarfileset dir="${project.distrib.dir}/linux" filemode="755" prefix="${release.name}"/>
 			<!-- Gitblit Authority data -->
-			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="data/certs" />
+			<zipfileset dir="${project.distrib.dir}/data/certs" prefix="${release.name}/data/certs" />
 			<!-- include all dependencies -->
-			<dependencies prefix="ext" />
+			<dependencies prefix="${release.name}/ext" />
 		</mx:tar>		
 
 	</target>
@@ -257,7 +261,7 @@
 		</mx:webxml>
 
 		<!-- Gitblit jar -->
-		<mx:jar destfile="${webinf}/lib/gitblit.jar" includeresources="false" />
+		<mx:jar destfile="${webinf}/lib/gitblit-${project.version}.jar" includeresources="false" />
 
 		<!-- Build the WAR file -->
 		<mx:zip basedir="${war.dir}" destfile="${project.targetDirectory}/${distribution.warfile}" compress="true" >
@@ -290,7 +294,7 @@
 			 classes, exclude any classes in classpath jars -->
 		<mx:genjar tag="" includeresources="false" excludeClasspathJars="true"
 			destfile="${project.targetDirectory}/fedclient.jar"
-			excludes="**/.class,**/*.java, **/Thumbs.db, **/*.mkd, com/gitblit/wicket/**">
+			excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
 			<mainclass name="com.gitblit.FederationClient" />
 			<class name="com.gitblit.Keys" />
 			<launcher paths="ext" />
@@ -330,7 +334,8 @@
 		<!-- generate jar by traversing the class hierarchy of the specified
 			 classes, exclude any classes in classpath jars -->
 		<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
-			destfile="${project.targetDirectory}/manager.jar">
+			destfile="${project.targetDirectory}/manager.jar"
+			excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
 			<resource file="${project.src.dir}/com/gitblit/client/splash.png" />
 			<resource file="${project.resources.dir}/gitblt-favicon.png" />
 			<resource file="${project.resources.dir}/gitweb-favicon.png" />
@@ -406,9 +411,10 @@
 
 		<!-- Build API Library jar -->
 		<mx:genjar tag="" includeResources="false" excludeClasspathJars="true"
-			destfile="${project.targetDirectory}/gbapi-${project.version}.jar">
+			destfile="${project.targetDirectory}/gbapi-${project.version}.jar"
+			excludes="**/.class, **/*.java, **/Thumbs.db, **/*.mkd, **/*.md, **/*.css, com/gitblit/wicket/**">
+			<mainclass name="com.gitblit.client.GitblitClient" />
 			<class name="com.gitblit.Keys" />
-			<class name="com.gitblit.client.GitblitClient" />
 			<class name="com.gitblit.models.FederationModel" />
 			<class name="com.gitblit.models.FederationProposal" />
 			<class name="com.gitblit.models.FederationSet" />			
@@ -507,6 +513,7 @@
 						<page name="bugtraq" src="setup_bugtraq.mkd" />
 						<page name="mirrors" src="setup_mirrors.mkd" />
 						<page name="scaling" src="setup_scaling.mkd" />
+						<page name="fail2ban" src="setup_fail2ban.mkd" />
 						<divider />
 						<page name="Gitblit as a viewer" src="setup_viewer.mkd" />
 					</menu>
@@ -546,8 +553,6 @@
 					<page name="release history" out="releases.html">
 						<template src="releasehistory.ftl" data="${releaselog}" />
 					</page>
-					<divider />
-					<page name="roadmap" src="roadmap.mkd" />					
 				</menu>
 				
 				<menu name="downloads">
@@ -578,7 +583,6 @@
 					<link name="Github" src="${project.scmUrl}" />
 					<link name="Issues" src="${project.issuesUrl}" />
 					<link name="Discussion" src="${project.forumUrl}" />
-					<link name="Google+" src="${project.socialNetworkUrl}" />
 					<link name="Twitter" src="https://twitter.com/gitblit" />
 					<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
                     <divider />
@@ -586,7 +590,6 @@
 					<link name="Gitblit SSH and Plugin Management asciicast" src="https://asciinema.org/a/9342" />
                     <link name="GitMinutes #29: James Moger on Gitblit" src="http://episodes.gitminutes.com/2014/05/gitminutes-29-james-moger-on-gitblit.html" />
 					<divider />
-					<link name="+JamesMoger" src="https://plus.google.com/+JamesMoger" />
 					<link name="@JamesMoger" src="https://twitter.com/JamesMoger" />
 				</menu>
 				<divider />
@@ -594,7 +597,7 @@
 			
 			<replace token="%GCURL%" value="${gc.url}" />
 			
-			<properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+			<properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" />
 			
 			<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="&lt;a href='http://code.google.com/p/gitblit/issues/detail?id=$3'&gt;issue $3&lt;/a&gt;" />
 			<regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="&lt;a href='https://github.com/gitblit/gitblit/pull/$3'&gt;pull request #$3&lt;/a&gt;" />
@@ -890,12 +893,11 @@
 						<link name="Github" src="${project.scmUrl}" />
 						<link name="Issues" src="${project.issuesUrl}" />
 						<link name="Discussion" src="${project.forumUrl}" />
-						<link name="Google+" src="${project.socialNetworkUrl}" />
 						<link name="Ohloh" src="http://www.ohloh.net/p/gitblit" />
 					</menu>
 				</structure>
 				
-				<properties token="%PROPERTIES%" file="${project.distrib.dir}/data/gitblit.properties" />
+				<properties token="%PROPERTIES%" file="${project.distrib.dir}/data/defaults.properties" />
 				
 				<regex searchPattern="\b(issue)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="&lt;a href='http://code.google.com/p/gitblit/issues/detail?id=$3'&gt;issue $3&lt;/a&gt;" />
 				<regex searchPattern="\b(pr|pull request)(\s*[#]?|-){0,1}(\d+)\b" replacePattern="&lt;a href='https://github.com/gitblit/gitblit/pull/$3'&gt;pull request #$3&lt;/a&gt;" />
@@ -935,6 +937,7 @@
 				<fileset dir="${project.distrib.dir}/data">
 					<include name="users.conf" />
 					<include name="projects.conf" />
+					<include name="defaults.properties" />
 					<include name="gitblit.properties" />					
 				</fileset>
 			</copy>
diff --git a/gitblit.iml b/gitblit.iml
index d6f84df..93331b2 100644
--- a/gitblit.iml
+++ b/gitblit.iml
@@ -7,20 +7,20 @@
     <content url="file://$MODULE_DIR$">
       <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/src/main/bugtraq" isTestSource="false" />
-      <sourceFolder url="file://$MODULE_DIR$/src/main/dagger" isTestSource="false" />
+      <sourceFolder url="file://$MODULE_DIR$/src/main/gen" isTestSource="false" />
       <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/src/test/bugtraq" isTestSource="true" />
       <sourceFolder url="file://$MODULE_DIR$/src/main/resources" isTestSource="false" />
     </content>
     <orderEntry type="sourceFolder" forTests="false" />
     <orderEntry type="module-library">
-      <library name="dagger-1.1.0.jar">
+      <library name="guice-4.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/dagger-1.1.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/guice-4.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/dagger-1.1.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/guice-4.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -36,24 +36,35 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="dagger-compiler-1.1.0.jar">
+      <library name="aopalliance-1.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/dagger-compiler-1.1.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/aopalliance-1.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/dagger-compiler-1.1.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/aopalliance-1.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="javawriter-2.1.1.jar">
+      <library name="guava-18.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/javawriter-2.1.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/guava-18.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/javawriter-2.1.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/guava-18.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="guice-servlet-4.0-gb2.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/guice-servlet-4.0-gb2.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/guice-servlet-4.0-gb2.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -80,24 +91,24 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="slf4j-api-1.7.5.jar">
+      <library name="slf4j-api-1.7.12.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/slf4j-api-1.7.5.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/slf4j-api-1.7.12.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.7.5.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/slf4j-api-1.7.12.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="slf4j-log4j12-1.7.5.jar">
+      <library name="slf4j-log4j12-1.7.12.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/slf4j-log4j12-1.7.5.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/slf4j-log4j12-1.7.12.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.7.5.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/slf4j-log4j12-1.7.12.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -124,123 +135,123 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="jetty-all-9.2.3.v20140905.jar">
+      <library name="jetty-all-9.2.13.v20150730.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/jetty-all-9.2.3.v20140905.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/jetty-all-9.2.13.v20150730.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jetty-all-9.2.3.v20140905.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jetty-all-9.2.13.v20150730.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="wicket-1.4.21.jar">
+      <library name="wicket-1.4.22.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/wicket-1.4.21.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/wicket-1.4.22.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.21.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/wicket-1.4.22.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="wicket-auth-roles-1.4.21.jar">
+      <library name="wicket-auth-roles-1.4.22.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/wicket-auth-roles-1.4.21.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/wicket-auth-roles-1.4.22.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.21.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/wicket-auth-roles-1.4.22.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="wicket-extensions-1.4.21.jar">
+      <library name="wicket-extensions-1.4.22.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/wicket-extensions-1.4.21.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/wicket-extensions-1.4.22.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.21.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/wicket-extensions-1.4.22.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-core-4.6.0.jar">
+      <library name="lucene-core-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-core-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-core-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-core-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-core-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-analyzers-common-4.6.0.jar">
+      <library name="lucene-analyzers-common-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-analyzers-common-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-analyzers-common-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-analyzers-common-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-analyzers-common-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-highlighter-4.6.0.jar">
+      <library name="lucene-highlighter-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-highlighter-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-highlighter-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-highlighter-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-memory-4.6.0.jar">
+      <library name="lucene-memory-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-memory-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-memory-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-memory-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-queries-4.6.0.jar">
+      <library name="lucene-queries-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-queries-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-queries-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-queries-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-queryparser-4.6.0.jar">
+      <library name="lucene-queryparser-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-queryparser-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-queryparser-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-queryparser-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-queryparser-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="lucene-sandbox-4.6.0.jar">
+      <library name="lucene-sandbox-4.10.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/lucene-sandbox-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/lucene-sandbox-4.10.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/lucene-sandbox-4.6.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/lucene-sandbox-4.10.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -254,79 +265,79 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="pegdown-1.4.2.jar">
+      <library name="pegdown-1.5.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/pegdown-1.4.2.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/pegdown-1.5.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/pegdown-1.4.2.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/pegdown-1.5.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="parboiled-java-1.1.6.jar">
+      <library name="parboiled-java-1.1.7.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/parboiled-java-1.1.6.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/parboiled-java-1.1.7.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/parboiled-java-1.1.6.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/parboiled-java-1.1.7.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="parboiled-core-1.1.6.jar">
+      <library name="parboiled-core-1.1.7.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/parboiled-core-1.1.6.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/parboiled-core-1.1.7.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/parboiled-core-1.1.6.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/parboiled-core-1.1.7.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="asm-4.1.jar">
+      <library name="asm-5.0.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/asm-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/asm-5.0.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/asm-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/asm-5.0.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="asm-tree-4.1.jar">
+      <library name="asm-tree-5.0.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/asm-tree-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/asm-tree-5.0.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/asm-tree-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/asm-tree-5.0.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="asm-analysis-4.1.jar">
+      <library name="asm-analysis-5.0.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/asm-analysis-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/asm-analysis-5.0.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/asm-analysis-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/asm-analysis-5.0.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="asm-util-4.1.jar">
+      <library name="asm-util-5.0.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/asm-util-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/asm-util-5.0.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/asm-util-4.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/asm-util-5.0.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -397,24 +408,24 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="org.eclipse.jgit-3.5.1.201410131835-r.jar">
+      <library name="org.eclipse.jgit-4.1.1.201511131810-r.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-3.5.1.201410131835-r.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit-4.1.1.201511131810-r.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-3.5.1.201410131835-r.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit-4.1.1.201511131810-r.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="jsch-0.1.50.jar">
+      <library name="jsch-0.1.53.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/jsch-0.1.50.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/jsch-0.1.53.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.50.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jsch-0.1.53.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -430,35 +441,35 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="httpclient-4.1.3.jar">
+      <library name="httpclient-4.3.6.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/httpclient-4.1.3.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/httpclient-4.3.6.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.1.3.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/httpclient-4.3.6.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="httpcore-4.1.4.jar">
+      <library name="httpcore-4.3.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/httpcore-4.1.4.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/httpcore-4.3.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.1.4.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/httpcore-4.3.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="commons-logging-1.1.1.jar">
+      <library name="commons-logging-1.1.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/commons-logging-1.1.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/commons-logging-1.1.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -474,68 +485,77 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar">
+      <library name="org.eclipse.jdt.annotation-1.1.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jdt.annotation-1.1.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES />
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library">
+      <library name="org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-3.5.1.201410131835-r.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/org.eclipse.jgit.http.server-4.1.1.201511131810-r.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="bcprov-jdk15on-1.49.jar">
+      <library name="bcprov-jdk15on-1.52.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.49.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/bcprov-jdk15on-1.52.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.49.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/bcprov-jdk15on-1.52.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="bcmail-jdk15on-1.49.jar">
+      <library name="bcmail-jdk15on-1.52.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.49.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/bcmail-jdk15on-1.52.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.49.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/bcmail-jdk15on-1.52.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="bcpkix-jdk15on-1.49.jar">
+      <library name="bcpkix-jdk15on-1.52.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.49.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/bcpkix-jdk15on-1.52.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.49.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/bcpkix-jdk15on-1.52.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="sshd-core-0.12.0.jar">
+      <library name="sshd-core-1.0.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/sshd-core-0.12.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/sshd-core-1.0.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/sshd-core-0.12.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/sshd-core-1.0.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="mina-core-2.0.7.jar">
+      <library name="mina-core-2.0.9.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/mina-core-2.0.7.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/mina-core-2.0.9.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/mina-core-2.0.7.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/mina-core-2.0.9.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -562,35 +582,35 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="gson-1.7.2.jar">
+      <library name="gson-2.3.1.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/gson-1.7.2.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/gson-2.3.1.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/gson-1.7.2.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/gson-2.3.1.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="groovy-all-1.8.8.jar">
+      <library name="groovy-all-2.4.4.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/groovy-all-1.8.8.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/groovy-all-2.4.4.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/groovy-all-1.8.8.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/groovy-all-2.4.4.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="unboundid-ldapsdk-2.3.0.jar">
+      <library name="unboundid-ldapsdk-2.3.8.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/unboundid-ldapsdk-2.3.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/unboundid-ldapsdk-2.3.8.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/unboundid-ldapsdk-2.3.8.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -681,90 +701,79 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="freemarker-2.3.19.jar">
+      <library name="freemarker-2.3.22.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.19.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/freemarker-2.3.22.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.19.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/freemarker-2.3.22.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="waffle-jna-1.5.jar">
+      <library name="waffle-jna-1.7.3.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.5.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/waffle-jna-1.7.3.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.5.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/waffle-jna-1.7.3.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="platform-3.5.0.jar">
+      <library name="jna-4.1.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/platform-3.5.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/jna-4.1.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/platform-3.5.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jna-4.1.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="jna-3.5.0.jar">
+      <library name="jna-platform-4.1.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/jna-3.5.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/jna-platform-4.1.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jna-3.5.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jna-platform-4.1.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="guava-13.0.1.jar">
+      <library name="libpam4j-1.8.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/guava-13.0.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/libpam4j-1.8.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/guava-13.0.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.8.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="libpam4j-1.7.jar">
+      <library name="args4j-2.0.29.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/libpam4j-1.7.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/args4j-2.0.29.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/libpam4j-1.7.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/args4j-2.0.29.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="args4j-2.0.26.jar">
+      <library name="jedis-2.6.2.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/args4j-2.0.26.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/jedis-2.6.2.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/args4j-2.0.26.jar!/" />
-        </SOURCES>
-      </library>
-    </orderEntry>
-    <orderEntry type="module-library">
-      <library name="jedis-2.3.1.jar">
-        <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/jedis-2.3.1.jar!/" />
-        </CLASSES>
-        <JAVADOC />
-        <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/jedis-2.3.1.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/jedis-2.6.2.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -780,13 +789,13 @@
       </library>
     </orderEntry>
     <orderEntry type="module-library">
-      <library name="pf4j-0.8.0.jar">
+      <library name="pf4j-0.9.0.jar">
         <CLASSES>
-          <root url="jar://$MODULE_DIR$/ext/pf4j-0.8.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/pf4j-0.9.0.jar!/" />
         </CLASSES>
         <JAVADOC />
         <SOURCES>
-          <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.8.0.jar!/" />
+          <root url="jar://$MODULE_DIR$/ext/src/pf4j-0.9.0.jar!/" />
         </SOURCES>
       </library>
     </orderEntry>
@@ -922,6 +931,39 @@
         </SOURCES>
       </library>
     </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="platform-3.4.0.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/platform-3.4.0.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/platform-3.4.0.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="mockito-core-1.10.19.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/mockito-core-1.10.19.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/mockito-core-1.10.19.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
+    <orderEntry type="module-library" scope="TEST">
+      <library name="objenesis-2.1.jar">
+        <CLASSES>
+          <root url="jar://$MODULE_DIR$/ext/objenesis-2.1.jar!/" />
+        </CLASSES>
+        <JAVADOC />
+        <SOURCES>
+          <root url="jar://$MODULE_DIR$/ext/src/objenesis-2.1.jar!/" />
+        </SOURCES>
+      </library>
+    </orderEntry>
     <orderEntry type="inheritedJdk" />
   </component>
 </module>
diff --git a/releases.moxie b/releases.moxie
index 2241384..7a3927d 100644
--- a/releases.moxie
+++ b/releases.moxie
@@ -9,11 +9,104 @@
     html: ~
     text: ~
     security: ~
-    fixes: ~
-    changes: ~
-    additions: ~
-    dependencyChanges: ~
-    contributors: ~
+    fixes:
+    - Fix exception when viewing a ticket with a patchset where the integration branch does not exist (issue-521, ticket-212)
+    - Fix exception when deleting a repository using the FileTicketService (issue-522, ticket-213)
+    - Do not inject team repository permissions as explicit user permissions when editing a user (issue-462, ticket-214)
+    - Whitelist the target link attribute in the XSS filter (ticket-216)    
+    - Strip line breaks from pasted SSH keys (ticket-245)
+    - Fix project sorting (pr-287)
+    - Fix Lucene indexing of tags (pr-291)
+    - Prevent session fixation for external authentication (pr-908)
+    - Encode email subject as UTF-8 (pr-929)
+    - Do not automatically trim passwords (pr-932)
+    - Fix nested repository detection in raw servlet (pr-950)
+    changes:
+    - Replaced Dagger with Guice (ticket-80)
+    - Use release name as root directory in Gitblit GO artifacts (ticket-109)
+    - Split gitblit.properties into gitblit.properties & defaults.properties (ticket-110)
+    - Show team type in teams page (pr-217, ticket-168)
+    - Relocate the repository Delete button (ticket-225)
+    - Improve diff performance by gracefully limiting large diffs (pr-226)
+    - Add granular settings to disable display of git transport urls (pr-274)
+    - Use author date to be consistent with other tools (pr-919)
+    additions:
+    - Add GitHub Octicons (ticket-106)
+    - Support for chain-loading properties files (ticket-110) 
+    - Add Priority & Severity fields for tickets (pr-220, ticket-157)
+    - Add Maintenance ticket type (pr-223, ticket-206)
+    - Add commitdiff option to ignore whitespace (ticket-233)
+    - Add configurable tab length for blob views (ticket-253)
+    - Implement image diffs (pr-229)
+    - Add support for configurable HTTP proxy host/port in PluginManager (pr-235)
+    - Implement collapsed empty folder navigation (pr-241)
+    - Implement hashing to detect usermodel changes and reduce users.conf file I/O (pr-246)
+    - Add support for Kerberos5/GSS authentication to SSH (pr-254)
+    - Allow extraction of additional user metadata in request headers when using external or container authentication (pr-255)
+    - Allow custom host & port specification for advertised SSH urls (pr-268)
+    - Improve logging for fail2ban usage (pr-296)
+    - Initial implementation of Git-LFS (pr-921)
+    - Add "all" repositories parameter to Search page (pr-935)
+    dependencyChanges:
+    - Guice 4.0 (ticket-80, ticket-219)
+    - SLF4j 1.7.12
+    - gson 2.3.1
+    - Freemarker 2.3.22
+    - Lucene 4.10.0 (ticket-159)
+    - SSHD 1.0.0
+    - JGit 4.1.1
+    - Groovy 2.4.4
+    - Wicket 1.4.22
+    - BouncyCastle 1.52
+    - Pegdown 1.5.0
+    - Jetty 9.2.13
+    settings:
+    - { name: web.displayUserPanel, defaultValue: 'true' }
+    - { name: web.tabLength, defaultValue: 4 }
+    - { name: web.avatarClass, defaultValue: '' }
+    - { name: web.showHttpServletUrls, defaultValue: 'true' }
+    - { name: web.showGitDaemonUrls, defaultValue: 'true' }
+    - { name: web.showSshDaemonUrls, defaultValue: 'true' }
+    - { name: web.advertiseAccessPermissionForOtherUrls, defaultValue: 'false' }
+    - { name: web.maxDiffLinesPerFile, defaultValue: '4000' }
+    - { name: web.maxDiffLines, defaultValue: '20000' }
+    - { name: ssh.advertisedHost, defaultValue: '' }
+    - { name: ssh.advertisedPort, defaultValue: '' }
+    - { name: git.sshWithKrb5, defaultValue: '' }
+    - { name: git.sshKrb5Keytab, defaultValue: '' }
+    - { name: git.sshKrb5ServicePrincipalName, defaultValue: '' }
+    - { name: git.sshKrb5StripDomain, defaultValue: 'true' }
+    - { name: filestore.storageFolder, defaultValue: '${baseFolder}/lfs' }
+    - { name: filestore.maxUploadSize, defaultValue: '-1' }
+    - { name: plugins.httpProxyHost, defaultValue: '' }
+    - { name: plugins.httpProxyPort, defaultValue: '' }
+    - { name: plugins.httpProxyAuthorization, defaultValue: '' }
+    - { name: realm.container.autoAccounts.displayName, defaultValue: '' }
+    - { name: realm.container.autoAccounts.emailAddress, defaultValue: '' }
+    - { name: realm.container.autoAccounts.locale, defaultValue: '' }
+    - { name: realm.container.autoAccounts.adminRole, defaultValue: '' }
+    
+    contributors:
+    - James Moger
+    - David Ostrovsky
+    - Alex Lewis
+    - Florian Zschocke
+    - Paul Martin
+    - razzard
+    - Alexander Zabluda
+    - Marcin Cieślak
+    - Rainer W
+    - Vitaliy Filippov
+    - willyann
+    - enrico204
+    - mrjoel
+    - Fabrice Bacchella
+    - Milos Cubrilo
+    - Thomas Wolf
+    - Morten Bøgeskov
+    - Steven Oliver
+    - Dariusz Bywalec
+    - Jan Šmucr
 }
 
 #
diff --git a/src/main/.gitignore b/src/main/.gitignore
index 01c48ab..0d04688 100644
--- a/src/main/.gitignore
+++ b/src/main/.gitignore
@@ -1 +1,2 @@
 /dagger
+/gen
diff --git a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
index 7776e6f..60b4ecc 100644
--- a/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
+++ b/src/main/bugtraq/com/syntevo/bugtraq/BugtraqConfig.java
@@ -214,7 +214,7 @@
 			}
 			finally {
 				rw.dispose();
-				tw.release();
+				tw.close();
 			}
 
 			if (content == null) {
diff --git a/src/main/distrib/data/defaults.properties b/src/main/distrib/data/defaults.properties
new file mode 100644
index 0000000..ce6267a
--- /dev/null
+++ b/src/main/distrib/data/defaults.properties
@@ -0,0 +1,2046 @@
+#
+# DEFAULTS.PROPERTIES
+#
+# The default Gitblit settings.
+#
+
+# This settings file supports parameterization from the command-line for the
+# following command-line parameters:
+#
+#   --baseFolder    ${baseFolder}    SINCE 1.2.1
+#
+# Settings that support ${baseFolder} parameter substitution are indicated with the
+# BASEFOLDER attribute.  If the --baseFolder argument is unspecified, ${baseFolder}
+# and it's trailing / will be discarded from the setting value leaving a relative
+# path that is equivalent to pre-1.2.1 releases.
+#
+# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified 
+#
+# Git Servlet Settings
+#
+
+# Base folder for repositories.
+# This folder may contain bare and non-bare repositories but Gitblit will only
+# allow you to push to bare repositories.
+# Use forward slashes even on Windows!!
+# e.g. c:/gitrepos
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+git.repositoriesFolder = ${baseFolder}/git
+
+# Build the available repository list at startup and cache this list for reuse.
+# This reduces disk io when presenting the repositories page, responding to rpcs,
+# etc, but it means that  Gitblit will not automatically identify repositories
+# added or deleted by external tools.
+#
+# For this case you can use curl, wget, etc to issue an rpc request to clear the
+# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE)
+#
+# SINCE 1.1.0
+git.cacheRepositoryList = true
+
+# Search the repositories folder subfolders for other repositories.
+# Repositories MAY NOT be nested (i.e. one repository within another)
+# but they may be grouped together in subfolders.
+# e.g. c:/gitrepos/libraries/mylibrary.git
+#      c:/gitrepos/libraries/myotherlibrary.git
+#
+# SINCE 0.5.0
+git.searchRepositoriesSubfolders = true
+
+# Maximum number of folders to recurse into when searching for repositories.
+# The default value, -1, disables depth limits.
+#
+# SINCE 1.1.0
+git.searchRecursionDepth = -1
+
+# List of regex exclusion patterns to match against folders found in
+# *git.repositoriesFolder*.
+# Use forward slashes even on Windows!!
+# e.g. test/jgit\.git
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.1.0
+git.searchExclusions =
+
+# List of regex url patterns for extracting a repository name when locating
+# submodules.
+#   e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract
+#   *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*
+# If no matches are found then the submodule repository name is assumed to be
+# whatever trails the last / character. (e.g. gitblit.git).
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.1.0
+git.submoduleUrlPatterns = .*?://github.com/(.*)
+
+# Specify the interface for Git Daemon to bind it's service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+git.daemonBindInterface = 
+
+# port for serving the Git Daemon service.  <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 9418
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+git.daemonPort = 9418
+
+# The port for serving the SSH service.  <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 29418
+#
+# SINCE 1.5.0
+# RESTART REQUIRED
+git.sshPort = 29418
+
+# Specify the interface for the SSH daemon to bind its service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.5.0
+# RESTART REQUIRED
+git.sshBindInterface = 
+
+# Manually specify the hostname to use in advertised SSH repository urls.
+# This may be useful in complex forwarding setups.
+#
+# SINCE 1.7.0
+git.sshAdvertisedHost = 
+
+# Manually specify the port to use in advertised SSH repository urls.
+# This may be useful in complex forwarding setups.
+#
+# SINCE 1.7.0
+git.sshAdvertisedPort = 
+
+# Specify the SSH key manager to use for retrieving, storing, and removing
+# SSH keys.
+#
+# Valid key managers are:
+#    com.gitblit.transport.ssh.FileKeyManager
+#
+# SINCE 1.5.0
+git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager
+
+# Directory for storing user SSH keys when using the FileKeyManager.
+#
+# SINCE 1.5.0
+git.sshKeysFolder= ${baseFolder}/ssh
+
+# Use Kerberos5 (GSS) authentication
+#
+# SINCE 1.7.0
+git.sshWithKrb5 = false
+
+# The path to a Kerberos 5 keytab.
+#
+# SINCE 1.7.0
+git.sshKrb5Keytab = 
+
+# The service principal name to be used for Kerberos5.
+# The default is host/hostname.
+#
+# SINCE 1.7.0
+git.sshKrb5ServicePrincipalName = 
+
+# Strip the domain suffix from a kerberos username.
+# e.g. james@bigbox would be "james"
+#
+# SINCE 1.7.0
+git.sshKrb5StripDomain = true
+
+# SSH backend NIO2|MINA.
+#
+# The Apache Mina project recommends using the NIO2 backend.
+#
+# SINCE 1.5.0
+git.sshBackend = NIO2
+
+# Number of threads used to parse a command line submitted by a client over SSH
+# for execution, create the internal data structures used by that command,
+# and schedule it for execution on another thread.
+#
+# SINCE 1.5.0
+git.sshCommandStartThreads = 2
+
+
+# Allow push/pull over http/https with JGit servlet.
+# If you do NOT want to allow Git clients to clone/push to Gitblit set this
+# to false.  You might want to do this if you are only using ssh:// or git://.
+# If you set this false, consider changing the *web.otherUrls* setting to
+# indicate your clone/push urls.
+#
+# SINCE 0.5.0
+git.enableGitServlet = true
+
+# If you want to restrict all git servlet access to those with valid X509 client
+# certificates then set this value to true.
+#
+# SINCE 1.2.0
+git.requiresClientCertificate = false
+
+# Enforce date checks on client certificates to ensure that they are not being
+# used prematurely and that they have not expired.
+#
+# SINCE 1.2.0
+git.enforceCertificateValidity = true
+
+# List of OIDs to extract from a client certificate DN to map a certificate to
+# an account username.
+#
+# e.g. git.certificateUsernameOIDs = CN
+# e.g. git.certificateUsernameOIDs = FirstName LastName
+#
+# SPACE-DELIMITED
+# SINCE 1.2.0
+git.certificateUsernameOIDs = CN
+
+# Only serve/display bare repositories.
+# If there are non-bare repositories in git.repositoriesFolder and this setting
+# is true, they will be excluded from the ui. 
+#
+# SINCE 0.9.0
+git.onlyAccessBareRepositories = false
+
+
+# Specify the list of acceptable transports for pushes.
+# If this setting is empty, all transports are acceptable.
+#
+# Valid choices are: GIT HTTP HTTPS SSH
+#
+# SINCE 1.5.0
+# SPACE-DELIMITED
+git.acceptedPushTransports = HTTP HTTPS SSH
+
+# Allow an authenticated user to create a destination repository on a push if
+# the repository does not already exist.
+#
+# Administrator accounts can create a repository in any project.
+# These repositories are created with the default access restriction and authorization
+# control values.  The pushing account is set as the owner.
+#
+# Non-administrator accounts with the CREATE role may create personal repositories.
+# These repositories are created as VIEW restricted for NAMED users.
+# The pushing account is set as the owner.
+#
+# SINCE 1.2.0
+git.allowCreateOnPush = true
+
+# Global setting to control anonymous pushes.
+#
+# This setting allows/rejects anonymous pushes at the level of the receive pack.
+# This trumps all repository config settings.  While anonymous pushes are convenient
+# on your own box when you are a lone developer,  they are not recommended for
+# any multi-user installation where accountability is required.  Since Gitblit
+# tracks pushes and user accounts, allowing anonymous pushes compromises that
+# information.
+#
+# SINCE 1.4.0
+git.allowAnonymousPushes = false
+
+# The default access restriction for new repositories.
+# Valid values are NONE, PUSH, CLONE, VIEW
+#  NONE = anonymous view, clone, & push
+#  PUSH = anonymous view & clone and authenticated push
+#  CLONE = anonymous view, authenticated clone & push
+#  VIEW = authenticated view, clone, & push
+#
+# SINCE 1.0.0
+git.defaultAccessRestriction = PUSH
+
+# The default authorization control for new repositories.
+# Valid values are AUTHENTICATED and NAMED
+#  AUTHENTICATED = any authenticated user is granted restricted access
+#  NAMED = only named users/teams are granted restricted access
+#
+# SINCE 1.1.0
+git.defaultAuthorizationControl = NAMED
+
+# The prefix for a users personal repository directory.
+#
+# Personal user repositories are created in this directory, named by the user name
+# prefixed with the userRepositoryPrefix. For eaxmple, a user 'john' would have his
+# personal repositories in the directory '~john'.
+#
+# Cannot be an empty string. Also, absolute paths are changed to relative paths by 
+# removing the first directory separator.
+#
+# It is not recommended to change this value AFTER your user's have created
+# personal repositories because it will break all permissions, ownership, and
+# repository push/pull operations. 
+#
+# RESTART REQUIRED
+# SINCE 1.4.0
+git.userRepositoryPrefix = ~
+
+# The default incremental push tag prefix.  Tag prefix applied to a repository
+# that has automatic push tags enabled and does not specify a custom tag prefix.
+#
+# If incremental push tags are enabled, the tips of each branch in the push will
+# be tagged with an increasing revision integer.
+#
+# e.g. refs/tags/r2345 or refs/tags/rev_2345 
+#
+# SINCE 1.3.0
+git.defaultIncrementalPushTagPrefix = r
+
+# Controls creating a repository as --shared on Unix servers.
+#
+# In an Unix environment where mixed access methods exist for shared repositories,
+# the repository should be created with 'git init --shared' to make sure that
+# it can be accessed e.g. via ssh (user git) and http (user www-data).
+#
+# Valid values are the values available for the '--shared' option. The the manual
+# page for 'git init' for more information on shared repositories.
+#
+# SINCE 1.4.0
+git.createRepositoriesShared = false
+
+# Directory for gitignore templates used during repository creation.
+#
+# SINCE 1.6.0
+git.gitignoreFolder = ${baseFolder}/gitignore
+
+# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
+#
+# USE AT YOUR OWN RISK!
+#
+# If enabled, the garbage collection executor scans all repositories once a day
+# at the hour of your choosing.  The GC executor will take each repository "offline",
+# one-at-a-time, to check if the repository satisfies it's GC trigger requirements.
+#
+# While the repository is offline it will be inaccessible from the web UI or from
+# any of the other services (git, rpc, rss, etc).
+#
+# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block,
+# especially on Windows systems, so if you are using other tools please coordinate
+# their usage with your GC Executor schedule or do not use this feature.
+#
+# The GC algorithm complex and the JGit team advises caution when using their
+# young implementation of GC.
+#
+# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics
+#
+# EXPERIMENTAL
+# SINCE 1.2.0
+# RESTART REQUIRED
+git.enableGarbageCollection = false
+
+# Hour of the day for the GC Executor to scan repositories.
+# This value is in 24-hour time.
+#
+# SINCE 1.2.0
+git.garbageCollectionHour = 0
+
+# The default minimum total filesize of loose objects to trigger early garbage
+# collection.
+#
+# You may specify a custom threshold for a repository in the repository's settings.
+# Common unit suffixes of k, m, or g are supported.
+#
+# SINCE 1.2.0
+git.defaultGarbageCollectionThreshold = 500k
+
+# The default period, in days, between GCs for a repository.  If the total filesize
+# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's
+# custom threshold, this period will be short-circuited. 
+#
+# e.g. if a repository collects 100KB of loose objects every day with a 500KB
+# threshold and a period of 7 days, it will take 5 days for the loose objects to
+# be collected, packed, and pruned.
+#
+# OR
+#
+# if a repository collects 10KB of loose objects every day with a 500KB threshold
+# and a period of 7 days, it will take the full 7 days for the loose objects to be
+# collected, packed, and pruned.
+#
+# You may specify a custom period for a repository in the repository's settings.
+#
+# The minimum value is 1 day since the GC Executor only runs once a day.
+#
+# SINCE 1.2.0
+git.defaultGarbageCollectionPeriod = 7
+
+# Gitblit can automatically fetch ref updates for a properly configured mirror
+# repository.
+#
+# Requirements:
+# 1. you must manually clone the repository using native git
+#    git clone --mirror git://somewhere.com/myrepo.git
+# 2. the "origin" remote must be the mirror source
+# 3. the "origin" repository must be accessible without authentication OR the
+#    credentials must be embedded in the origin url (not recommended)
+#
+# Notes:
+# 1. "origin" SSH urls are untested and not likely to work
+# 2. mirrors cloned while Gitblit is running are likely to require clearing the
+#    gitblit cache (link on the repositories page of an administrator account)
+# 3. Gitblit will automatically repair any invalid fetch refspecs with a "//"
+#    sequence.
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+git.enableMirroring = false
+
+# Specify the period between update checks for mirrored repositories.
+# The shortest period you may specify between mirror update checks is 5 mins.
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+git.mirrorPeriod = 30 mins
+
+# Number of bytes of a pack file to load into memory in a single read operation.
+# This is the "page size" of the JGit buffer cache, used for all pack access
+# operations. All disk IO occurs as single window reads. Setting this too large
+# may cause the process to load more data than is required; setting this too small
+# may increase the frequency of read() system calls.
+#
+# Default on JGit is 8 KiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitWindowSize = 8k
+
+# Maximum number of bytes to load and cache in memory from pack files. If JGit
+# needs to access more than this many bytes it will unload less frequently used
+# windows to reclaim memory space within the process. As this buffer must be shared
+# with the rest of the JVM heap, it should be a fraction of the total memory available.
+#
+# The JGit team recommends setting this value larger than the size of your biggest
+# repository. This ensures you can serve most requests from memory.
+#
+# Default on JGit is 10 MiB on all platforms.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitLimit = 10m
+
+# Maximum number of bytes to reserve for caching base objects that multiple deltafied
+# objects reference. By storing the entire decompressed base object in a cache Git
+# is able to avoid unpacking and decompressing frequently used base objects multiple times.
+#
+# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust
+# this value.
+#
+# Common unit suffixes of k, m, or g are supported.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.deltaBaseCacheLimit = 10m
+
+# Maximum number of pack files to have open at once. A pack file must be opened
+# in order for any of its data to be available in a cached window.
+#
+# If you increase this to a larger setting you may need to also adjust the ulimit
+# on file descriptors for the host JVM, as Gitblit needs additional file descriptors
+# available for network sockets and other repository data manipulation.
+#
+# Default on JGit is 128 file descriptors on all platforms.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitOpenFiles = 128
+
+# When true, JGit will use mmap() rather than malloc()+read() to load data from
+# pack files.  The use of mmap can be problematic on some JVMs as the garbage
+# collector must deduce that a memory mapped segment is no longer in use before
+# a call to munmap() can be made by the JVM native code.
+#
+# In server applications (such as Gitblit) that need to access many pack files,
+# setting this to true risks artificially running out of virtual address space, 
+# as the garbage collector cannot reclaim unused mapped spaces fast enough.
+#
+# Default on JGit is false. Although potentially slower, it yields much more
+# predictable behavior.
+# Documentation courtesy of the Gerrit project.
+#
+# SINCE 1.0.0
+# RESTART REQUIRED
+git.packedGitMmap = false
+
+# Validate all received (pushed) objects are valid.
+#
+# SINCE 1.5.0
+git.checkReceivedObjects = true
+
+# Validate all referenced but not supplied objects are reachable.
+#
+# If enabled, Gitblit will verify that references to objects not contained
+# within the received pack are already reachable through at least one other
+# reference advertised to clients.
+#
+# This feature is useful when Gitblit doesn't trust the client to not provide a
+# forged SHA-1 reference to an object, in an attempt to access parts of the DAG
+# that they aren't allowed to see and which have been hidden from them via the
+# configured AdvertiseRefsHook or RefFilter.
+#
+# Enabling this feature may imply at least some, if not all, of the same functionality
+# performed by git.checkReceivedObjects. 
+#
+# SINCE 1.5.0
+git.checkReferencedObjectsAreReachable = true
+
+# Set the maximum allowed Git object size.
+#
+# If an object is larger than the given size the pack-parsing will throw an exception
+# aborting the receive-pack operation.  The default value, 0, disables maximum
+# object size checking.
+#
+# SINCE 1.5.0
+git.maxObjectSizeLimit = 0
+
+# Set the maximum allowed pack size.
+#
+# A pack exceeding this size will be rejected. The default value, -1, disables
+# maximum pack size checking.
+#
+# SINCE 1.5.0
+git.maxPackSizeLimit = -1
+
+# Use the Gitblit patch receive pack for processing contributions and tickets.
+# This allows the user to push a patch using the familiar Gerrit syntax:
+#
+#    git push <remote> HEAD:refs/for/<targetBranch>
+#
+# NOTE:
+# This requires git.enableGitServlet = true AND it requires an authenticated
+# git transport connection (http/https) when pushing from a client.
+#
+# Valid services include:
+#    com.gitblit.tickets.FileTicketService
+#    com.gitblit.tickets.BranchTicketService
+#    com.gitblit.tickets.RedisTicketService
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+tickets.service = 
+
+# Globally enable or disable creation of new bug, enhancement, task, etc tickets
+# for all repositories.
+#
+# If false, no tickets can be created through the ui for any repositories.
+# If true, each repository can control if they allow new tickets to be created.
+#
+# NOTE:
+# If a repository is accepting patchsets, new proposal tickets can be created
+# regardless of this setting.
+#
+# SINCE 1.4.0
+tickets.acceptNewTickets = true
+
+# Globally enable or disable pushing patchsets to all repositories.
+#
+# If false, no patchsets will be accepted for any repositories.
+# If true, each repository can control if they accept new patchsets.
+#
+# NOTE:
+# If a repository is accepting patchsets, new proposal tickets can be created
+# regardless of the acceptNewTickets setting.
+#
+# SINCE 1.4.0
+tickets.acceptNewPatchsets = true
+
+# Default setting to control patchset merge through the web ui.  If true, patchsets
+# must have an approval score to enable the merge button.  This setting can be
+# overriden per-repository.
+#
+# SINCE 1.4.0
+tickets.requireApproval = false
+
+# The case-insensitive regular expression used to identify and close tickets on
+# push to the integration branch for commits that are NOT already referenced as
+# a patchset tip.
+#
+# SINCE 1.5.0
+tickets.closeOnPushCommitMessageRegex = (?:fixes|closes)[\\s-]+#?(\\d+)
+
+# Specify the location of the Lucene Ticket index
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+tickets.indexFolder = ${baseFolder}/tickets/lucene
+
+# Define the url for the Redis server.
+#
+# e.g. redis://localhost:6379
+#      redis://:foobared@localhost:6379/2
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+tickets.redis.url =
+
+# The number of tickets to display on a page.
+#
+# SINCE 1.4.0
+tickets.perPage = 25
+
+# The folder where plugins are loaded from.
+#
+# SINCE 1.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+plugins.folder = ${baseFolder}/plugins
+
+# The registry of available plugins.
+#
+# SINCE 1.5.0
+plugins.registry = http://plugins.gitblit.com/plugins.json
+
+# The HTTP proxy host for plugin manager.
+#
+# SINCE 1.7.0
+plugins.httpProxyHost = 
+
+# The HTTP proxy port for plugin manager.
+#
+# SINCE 1.7.0
+plugins.httpProxyPort = 
+
+# The HTTP proxy authorization header for plugin manager.
+#
+# SINCE 1.7.0
+plugins.httpProxyAuthorization = 
+
+# Number of threads used to handle miscellaneous tasks in the background.
+#
+# SINCE 1.6.0
+# RESTART REQUIRED
+execution.defaultThreadPoolSize = 1
+
+#
+# Groovy Integration
+#
+
+# Location of Groovy scripts to use for Pre and Post receive hooks.
+# Use forward slashes even on Windows!!
+# e.g. c:/groovy
+#
+# RESTART REQUIRED
+# SINCE 0.8.0
+# BASEFOLDER
+groovy.scriptsFolder = ${baseFolder}/groovy
+
+# Specify the directory Grape uses for downloading libraries.
+# http://groovy.codehaus.org/Grape
+#
+# RESTART REQUIRED
+# SINCE 1.0.0
+# BASEFOLDER
+groovy.grapeFolder = ${baseFolder}/groovy/grape
+
+# Scripts to execute on Pre-Receive.
+#
+# These scripts execute after an incoming push has been parsed and validated
+# but BEFORE the changes are applied to the repository.  You might reject a
+# push in this script based on the repository and branch the push is attempting
+# to change.
+#
+# Script names are case-sensitive on case-sensitive file systems.  You may omit
+# the traditional ".groovy" from this list if your file extension is ".groovy" 
+#
+# NOTE:
+# These scripts are only executed when pushing to *Gitblit*, not to other Git
+# tooling you may be using.  Also note that these scripts are shared between
+# repositories. These are NOT repository-specific scripts!  Within the script
+# you may customize the control-flow for a specific repository by checking the
+# *repository* variable.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.8.0
+groovy.preReceiveScripts =
+
+# Scripts to execute on Post-Receive.
+#
+# These scripts execute AFTER an incoming push has been applied to a repository.
+# You might trigger a continuous-integration build here or send a notification.
+#
+# Script names are case-sensitive on case-sensitive file systems.  You may omit
+# the traditional ".groovy" from this list if your file extension is ".groovy" 
+#
+# NOTE:
+# These scripts are only executed when pushing to *Gitblit*, not to other Git
+# tooling you may be using.  Also note that these scripts are shared between
+# repositories. These are NOT repository-specific scripts!  Within the script
+# you may customize the control-flow for a specific repository by checking the
+# *repository* variable.
+# 
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.8.0
+groovy.postReceiveScripts =
+
+# Repository custom fields for Groovy Hook mechanism
+#
+# List of key=label pairs of custom fields to prompt for in the Edit Repository
+# page.  These keys are stored in the repository's git config file in the 
+# section [gitblit "customFields"].  Key names are alphanumeric only.  These
+# fields are intended to be used for the Groovy hook mechanism where a script
+# can adjust it's execution based on the custom fields stored in the repository
+# config.
+#
+# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+groovy.customFields = 
+
+#
+# Fanout Settings
+#
+
+# Fanout is a PubSub notification service that can be used by Sparkleshare
+# to eliminate repository change polling.  The fanout service runs in a separate
+# thread on a separate port from the Gitblit http/https application.
+# This service is provided so that Sparkleshare may be used with Gitblit in
+# firewalled environments or where reliance on Sparkleshare's default notifications
+# server (notifications.sparkleshare.org) is unwanted.
+#
+# This service maintains an open socket connection from the client to the
+# Fanout PubSub service. This service may not work properly behind a proxy server.  
+
+# Specify the interface for Fanout to bind it's service.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.bindInterface = 
+
+# port for serving the Fanout PubSub service.  <= 0 disables this service.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 17000
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.port = 0
+
+# Use Fanout NIO service.  If false, a multi-threaded socket service will be used.
+# Be advised, the socket implementation spawns a thread per connection plus the
+# connection acceptor thread.  The NIO implementation is completely single-threaded.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.useNio = true
+
+# Concurrent connection limit.  <= 0 disables concurrent connection throttling.
+# If > 0, only the specified number of concurrent connections will be allowed
+# and all other connections will be rejected.
+#
+# SINCE 1.2.1
+# RESTART REQUIRED
+fanout.connectionLimit = 0
+
+#
+# Authentication Settings
+#
+
+# Require authentication to see everything but the admin pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateViewPages = false
+
+# If web.authenticateViewPages=true you may optionally require a client-side
+# basic authentication prompt instead of the standard form-based login. 
+#
+# SINCE 1.3.0
+web.enforceHttpBasicAuthentication = false
+
+# Require admin authentication for the admin functions and pages
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.authenticateAdminPages = true
+
+# Allow Gitblit to store a cookie in the user's browser for automatic
+# authentication.  The cookie is generated by the user service.
+#
+# SINCE 0.5.0
+web.allowCookieAuthentication = true
+
+# Allow deletion of non-empty repositories. This is enforced for all delete vectors.
+#
+# SINCE 1.6.0
+web.allowDeletingNonEmptyRepositories = true
+
+# Setting to include personal repositories in the main repositories list.
+#
+# SINCE 1.6.0
+web.includePersonalRepositories = false
+
+# Config file for storing project metadata
+#
+# SINCE 1.2.0
+# BASEFOLDER
+web.projectsFile = ${baseFolder}/projects.conf
+
+# Defines the tab length for all blob views
+#
+# SINCE 1.7.0
+web.tabLength = 4
+
+# Either the full path to a user config file (users.conf)
+# OR a fully qualified class name that implements the IUserService interface.
+#
+# Any custom user service implementation must have a public default constructor.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+realm.userService = ${baseFolder}/users.conf
+
+# Ordered list of external authentication providers which will be used if
+# authentication against the local user service fails.
+#
+# Valid providers are:
+#
+#    htpasswd
+#    ldap
+#    pam
+#    redmine
+#    salesforce
+#    windows
+
+# e.g. realm.authenticationProviders = htpasswd windows
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+# SPACE-DELIMITED
+realm.authenticationProviders =
+
+# How to store passwords.
+# Valid values are plain, md5, or combined-md5.  md5 is the hash of password.
+# combined-md5 is the hash of username.toLowerCase()+password.
+# Default is md5.
+#
+# SINCE 0.5.0 
+realm.passwordStorage = md5
+
+# Minimum valid length for a plain text password.
+# Default value is 5.  Absolute minimum is 4.
+#
+# SINCE 0.5.0 
+realm.minPasswordLength = 5
+
+#
+# Gitblit Web Settings
+#
+# If blank Gitblit is displayed.
+#
+# SINCE 0.5.0
+web.siteName =
+
+# 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 = 
+
+# You may specify a different logo image for the header but it must be 120x45px.
+# If the specified file does not exist, the default Gitblit logo will be used.
+#
+# SINCE 1.3.0
+# BASEFOLDER
+web.headerLogo = ${baseFolder}/logo.png
+
+# You may specify a different link URL for the logo image anchor.
+# If blank the Gitblit main page URL is used.
+#
+# SINCE 1.3.0
+# BASEFOLDER
+web.rootLink =
+
+# You may specify a custom header background CSS color.  If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerBackgroundColor = #002060
+#
+# SINCE 1.3.0
+web.headerBackgroundColor =
+
+# You may specify a custom header foreground CSS color.  If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerForegroundColor = white
+#
+# SINCE 1.3.0
+web.headerForegroundColor =
+
+# You may specify a custom header foreground hover CSS color.  If unspecified, the
+# default color will be used.
+#
+# e.g. web.headerHoverColor = white
+#
+# SINCE 1.3.0
+web.headerHoverColor =
+
+# You may specify a custom header border CSS color.  If unspecified, the default
+# color will be used.
+#
+# e.g. web.headerBorderColor = #002060
+#
+# SINCE 1.3.0
+web.headerBorderColor =
+
+# You may specify a custom header border CSS color.  If unspecified, the default
+# color will be used.
+#
+# e.g. web.headerBorderFocusColor = #ff9900
+#
+# SINCE 1.3.0
+web.headerBorderFocusColor =
+
+# If *web.authenticateAdminPages*=true, users with "admin" role can create
+# repositories, create users, and edit repository metadata.
+#
+# If *web.authenticateAdminPages*=false, any user can execute the aforementioned
+# functions. 
+#
+# SINCE 0.5.0 
+web.allowAdministration = true
+
+# Setting to disable rendering the top-level navigation header which includes
+# the login form, top-level links like dashboard, repositories, search, etc.
+# This setting is only useful if you plan to embed Gitblit within another page
+# or system.
+#
+# SINCE 1.4.0
+web.hideHeader = false
+
+# Allows rpc clients to list repositories and possibly manage or administer the 
+# Gitblit server, if the authenticated account has administrator permissions.
+# See *web.enableRpcManagement* and *web.enableRpcAdministration*.
+#
+# SINCE 0.7.0 
+web.enableRpcServlet = true
+
+# Allows rpc clients to manage repositories and users of the Gitblit instance,
+# if the authenticated account has administrator permissions.
+# Requires *web.enableRpcServlet=true*.
+#
+# SINCE 0.7.0 
+web.enableRpcManagement = false
+
+# Allows rpc clients to control the server settings and monitor the health of this
+# this Gitblit instance, if the authenticated account has administrator permissions.
+# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*.
+#
+# SINCE 0.7.0 
+web.enableRpcAdministration = false
+
+# Full path to a configurable robots.txt file.  With this file you can control
+# what parts of your Gitblit server respectable robots are allowed to traverse.
+# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
+#
+# SINCE 1.0.0
+# BASEFOLDER
+web.robots.txt = ${baseFolder}/robots.txt
+
+# The number of minutes to cache a page in the browser since the last request.
+# The default value is 0 minutes.  A value <= 0 disables all page caching which
+# is the default behavior for Gitblit <= 1.3.0.
+#
+# SINCE 1.3.1
+web.pageCacheExpires = 0
+
+# If true, the web ui layout will respond and adapt to the browser's dimensions.
+# if false, the web ui will use a 940px fixed-width layout.
+# http://twitter.github.com/bootstrap/scaffolding.html#responsive
+#
+# SINCE 1.0.0
+web.useResponsiveLayout = true
+
+# Allow Gravatar images to be displayed in Gitblit pages.
+#
+# SINCE 0.8.0
+web.allowGravatar = true
+
+# Define which class will generate the avatar URL.
+#
+# SINCE 1.7.0
+web.avatarClass = com.gitblit.GravatarGenerator
+
+# Allow dynamic zip downloads.
+#
+# SINCE 0.5.0   
+web.allowZipDownloads = true
+
+# If *web.allowZipDownloads=true* the following formats will be displayed for
+# download compressed archive links:
+#
+# zip   = standard .zip
+# tar   = standard tar format (preserves *nix permissions and symlinks)
+# gz    = gz-compressed tar
+# xz    = xz-compressed tar
+# bzip2 = bzip2-compressed tar
+#
+# SPACE-DELIMITED
+# SINCE 1.2.0
+web.compressedDownloads = zip gz
+
+# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
+# A repository may specify branches to index with Lucene instead of using Git
+# commit traversal. There are scenarios where you may want to completely disable
+# Lucene indexing despite a repository specifying indexed branches.  One such
+# scenario is on a resource-constrained federated Gitblit mirror.
+#
+# SINCE 0.9.0
+web.allowLuceneIndexing = true
+
+# Control the frequency of Lucene repository indexing.
+# The default setting is to check for updated refs every 2 mins.
+#
+# SINCE 1.6.1
+web.luceneFrequency = 2 mins
+
+# Allows an authenticated user to create forks of a repository
+#
+# set this to false if you want to disable all fork controls on the web site
+#
+web.allowForking = true
+
+# Controls the length of shortened commit hash ids
+#
+# SINCE 1.2.0
+web.shortCommitIdLength = 6
+
+# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
+# If false, a button with a more primitive JavaScript-based prompt box will
+# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
+#
+# SINCE 0.8.0
+web.allowFlashCopyToClipboard = true
+
+# Default maximum number of commits that a repository may contribute to the
+# activity page, regardless of the selected duration.  This setting may be valuable
+# for an extremely busy server.  This value may also be configed per-repository
+# in Edit Repository. 0 disables this throttle.
+#
+# SINCE 1.2.0
+web.maxActivityCommits = 0
+
+# Default number of entries to include in RSS Syndication links
+#
+# SINCE 0.5.0
+web.syndicationEntries = 25
+
+# Show the size of each repository on the repositories page.
+# This requires recursive traversal of each repository folder.  This may be
+# non-performant on some operating systems and/or filesystems. 
+#
+# SINCE 0.5.2
+web.showRepositorySizes = true
+
+# List of custom regex expressions that can be displayed in the Filters menu
+# of the Repositories and Activity pages.  Keep them very simple because you
+# are likely to run into encoding issues if they are too complex.
+#
+# Use !!! to separate the filters 
+#
+# SINCE 0.8.0
+web.customFilters =
+
+# Show federation registrations (without token) and the current pull status
+# to non-administrator users. 
+#
+# SINCE 0.6.0
+web.showFederationRegistrations = false
+
+# This is the message displayed when *web.authenticateViewPages=true*.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal login message.
+#
+# SINCE 0.7.0
+# BASEFOLDER
+web.loginMessage = gitblit
+
+# This is the message displayed above the repositories table.
+# This can point to a file with Markdown content.
+# Specifying "gitblit" uses the internal welcome message.
+#
+# SINCE 0.5.0
+# BASEFOLDER
+web.repositoriesMessage = gitblit
+
+# Ordered list of charsets/encodings to use when trying to display a blob.
+# If empty, UTF-8 and ISO-8859-1 are used.  The server's default charset
+# is always appended to the encoding list.  If all encodings fail to cleanly
+# decode the blob content, UTF-8 will be used with the standard malformed
+# input/unmappable character replacement strings.
+# 
+# SPACE-DELIMITED
+# SINCE 1.0.0
+web.blobEncodings = UTF-8 ISO-8859-1
+
+# Manually set the default timezone to be used by Gitblit for display in the 
+# web ui.  This value is independent of the JVM timezone.  Specifying a blank
+# value will default to the JVM timezone.
+# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin
+#
+# SINCE 0.9.0
+# RESTART REQUIRED
+web.timezone =
+
+# Use the client timezone when formatting dates.
+# This uses AJAX to determine the browser's timezone and may require more
+# server overhead because a Wicket session is created.  All Gitblit pages
+# attempt to be stateless, if possible.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.useClientTimezone = false
+
+# Time format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.8.0
+web.timeFormat = HH:mm
+
+# Short date format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.5.0
+web.datestampShortFormat = yyyy-MM-dd
+
+# Long date format
+#
+# SINCE 0.8.0
+web.datestampLongFormat = EEEE, MMMM d, yyyy
+
+# Long timestamp format
+# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
+#
+# SINCE 0.5.0
+web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z
+
+# Mount URL parameters
+# This setting controls if pretty or parameter URLs are used.
+# i.e.
+# if true:
+#     http://localhost/commit/myrepo/abcdef
+# if false:
+#     http://localhost/commit/?r=myrepo&h=abcdef
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.mountParameters = true
+
+# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding
+# in URLs as a security precaution for proxies.  This setting tells Gitblit
+# to preemptively replace '/' with '*' or '!' for url string parameters.
+#
+# <https://issues.apache.org/jira/browse/WICKET-1303>
+# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
+# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your
+# *CATALINA_OPTS* or to your JVM launch parameters
+#
+# SINCE 0.5.2
+web.forwardSlashCharacter = /
+
+# Show other URLs on the summary page for accessing your git repositories
+# Use spaces to separate urls.
+#
+# {0} is the token for the repository name
+# {1} is the token for the username
+#
+# The username is only practical if you have setup your other git serving
+# solutions accounts to have the same username as the Gitblit account.
+#
+# e.g.
+# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0}
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.otherUrls = 
+
+# Should HTTP/HTTPS URLs be displayed if the git servlet is enabled?
+# default: true
+#
+# SINCE 1.7.0
+web.showHttpServletUrls = true
+
+# Should git URLs be displayed if the git daemon is enabled?
+# default: true
+#
+# SINCE 1.7.0
+web.showGitDaemonUrls = true
+
+# Should SSH URLs be displayed if the SSH daemon is enabled?
+# default: true
+#
+# SINCE 1.7.0
+web.showSshDaemonUrls = true
+
+# Should effective permissions be advertised for access paths defined in web.otherUrls?
+# If false, gitblit will indicate unknown permissions for the external link. If true,
+# gitblit will indicate permissions as defined within gitblit (including limiting to clone
+# permission is the transport type is not a valid push mechaism in git.acceptedPushTransports).
+#
+# Configure with caution: Note that gitblit has no way of knowing if further restrictions
+# are imposed by an external forwarding agent, so this may cause user confusion due to
+# more rights being advertised than are available through the URL. It will NOT grant
+# additional rights, but may incorrectly offer actions that are unavailable externally.
+# default: false
+#
+# SINCE 1.7.0
+web.advertiseAccessPermissionForOtherUrls = false
+
+# 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)
+#
+# SINCE 0.5.0
+web.repositoryListType = grouped
+
+# If using a grouped repository list and there are repositories at the
+# root level of your repositories folder, you may specify the displayed
+# group name with this setting.  This value is only used for web presentation.
+#
+# SINCE 0.5.0
+web.repositoryRootGroupName = main
+
+# Display the repository swatch color next to the repository name link in the 
+# repositories list. 
+#
+# SINCE 0.8.0
+web.repositoryListSwatches = true
+
+# Defines the default commit message renderer.  This can be configured
+# per-repository.
+#
+# Valid values are: plain, markdown
+#
+# SINCE 1.4.0
+web.commitMessageRenderer = plain
+
+# Control if email addresses are shown in web ui
+#
+# SINCE 0.5.0
+web.showEmailAddresses = true
+
+# Shows a combobox in the page links header with commit, committer, and author
+# search selection.  Default search is commit.
+#
+# SINCE 0.5.0
+web.showSearchTypeSelection = false
+
+# Controls display of activity graphs on the dashboard, activity, and summary
+# pages.  Charts are generated using Flotr2; an open source HTML5 library.
+#
+# SINCE 0.5.0 
+web.generateActivityGraph = true
+
+# Displays the commits branch graph in the summary page and commits/log page.
+#
+# SINCE 1.4.0
+web.showBranchGraph = true
+
+# The default number of days to show on the activity page.
+# Value must exceed 0 else default of 7 is used
+#
+# SINCE 0.8.0
+web.activityDuration = 7
+
+# Choices for days of activity to display.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.activityDurationChoices = 1 3 7 14 21 28
+
+# Maximum number of days of activity that may be displayed on the activity page.
+#
+# SINCE 1.3.2
+web.activityDurationMaximum = 30
+
+# The number of days of commits to cache in memory for the dashboard, activity,
+# and project pages.  A value of 0 will disable all caching and will parse commits
+# in each repository per-request.  If the value > 0 these pages will try to fulfill
+# requests using the commit cache.  If the request specifies a period which falls
+# outside the commit cache window, then the cache will be ignored and the request
+# will be fulfilled by brute-force parsing all relevant commits per-repository.
+#
+# Consider the values specified for *web.activityDurationChoices* when setting
+# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+web.activityCacheDays = 14
+
+# Case-insensitive list of authors to exclude from metrics.  Useful for
+# eliminating bots.
+#
+# SPACE-DELIMITED
+# SINCE 1.3.0
+web.metricAuthorExclusions =
+
+# The number of commits to display on the summary page
+# Value must exceed 0 else default of 20 is used
+#
+# SINCE 0.5.0
+web.summaryCommitCount = 16
+
+# The number of tags/branches to display on the summary page.
+# -1 = all tags/branches
+# 0 = hide tags/branches
+# N = N tags/branches
+#
+# SINCE 0.5.0
+web.summaryRefsCount = 5
+
+# Show a README file, if available, on the summary page.
+#
+# SINCE 1.4.0
+web.summaryShowReadme = false
+
+# The number of items to show on a page before showing the first, prev, next
+# pagination links.  A default of 50 is used for any invalid value.
+#
+# SINCE 0.5.0
+web.itemsPerPage = 50
+
+# The number of reflog changes to display on the overview page
+# Value must exceed 0 else default of 5 is used
+#
+# SINCE 1.3.0
+web.overviewReflogCount = 5
+
+# The number of reflog changes to show on a reflog page before show the first,
+#  prev, next pagination links.  A default of 10 is used for any invalid value.
+#
+# SINCE 1.3.0
+web.reflogChangesPerPage = 10
+
+# Specify the names of documents in the root of your repository to be displayed
+# in tabs on your repository docs page.  If the name is not found in the root
+# then no tab is added.  The order specified is the order displayed.  Do not
+# specify a file extension as the aggregation of markup extensions + txt are used
+# in the search algorithm.
+#
+# SPACE-DELIMITED
+# SINCE 1.4.0
+web.documents = readme home index changelog contributing submitting_patches copying license notice authors
+
+# Registered file extensions to ignore during Lucene indexing
+#
+# SPACE-DELIMITED
+# SINCE 0.9.0
+web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt pptx png so swf tar xcf xls xlsx zip
+
+# Registered extensions for google-code-prettify
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.prettyPrintExtensions = aea agc basic bat c cbm cl clj cmd cpp cs css dart el erl erlang frm fs go groovy h hpp hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl pm prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo
+
+# Registered extensions for markdown transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.5.0
+web.markdownExtensions = md mkd markdown MD MKD
+
+# Registered extensions for mediawiki transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.mediawikiExtensions = mw mediawiki
+
+# Registered extensions for twiki transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.twikiExtensions = twiki
+
+# Registered extensions for textile transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.textileExtensions = textile
+
+# Registered extensions for confluence transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.confluenceExtensions = confluence
+
+# Registered extensions for tracwiki transformation
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 1.4.0
+web.tracwikiExtensions = tracwiki
+
+# Image extensions
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.imageExtensions = bmp ico gif jpg jpeg png svg
+
+# Registered extensions for binary blobs
+#
+# SPACE-DELIMITED
+# SINCE 0.5.0
+web.binaryExtensions = 7z arc arj bin dll doc docx exe gz jar lib lzh odg odf odt pdf ppt pptx so tar xls xlsx zip
+
+# Aggressive heap management will run the garbage collector on every generated
+# page.  This slows down page generation a little but improves heap consumption. 
+#
+# SINCE 0.5.0
+web.aggressiveHeapManagement = false
+
+# Run the webapp in debug mode
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+web.debugMode = false
+
+# Allows to hide the user logon form or dropdown menu from the top pane 
+# if it's not needed.
+#
+# SINCE 1.7.0
+web.displayUserPanel = true
+
+# Force a default locale for all users, ignoring the browser's settings.
+# An empty value allows Gitblit to use the translation preferred by the browser.
+#
+# Changing this value while the server is running will only affect new sessions.
+#
+# e.g. web.forceDefaultLocale = en
+#
+# SINCE 1.3.0
+web.forceDefaultLocale = 
+
+# The following two settings serve to avoid browser overload when trying to
+# render very large diffs. Both limits apply to commitdiffs, not to single-file
+# diffs.
+
+# Maximum number of diff lines to display for a single file diff in a commitdiff.
+# Defaults to 4000; can be adjusted in the range [500 .. 4000]. Smaller values
+# set the limit to 500, larger values to 4000. The count includes context lines
+# in the diff.
+# 
+# If a file diff in a commitdiff produces more lines, the diff for that file is
+# not shown in the commitdiff.
+#
+# SINCE 1.7.0
+web.maxDiffLinesPerFile = 4000
+
+# Total maximum number of diff lines to show in a commitdiff. Defaults to 20000;
+# can be adjusted in the range [1000 .. 20000]. Smaller values set the limit to
+# 1000, larger values to 20000. The count includes context lines in diffs.
+#
+# If a commitdiff produces more lines, it is truncated after the first file
+# that exceeds the limit. Diffs for subsequent files in the commit are not shown
+# at all in the commitdiff. Omitted files are listed, though.
+#
+# SINCE 1.7.0
+web.maxDiffLines = 20000
+
+# Enable/disable global regex substitutions (i.e. shared across repositories)
+#
+# SINCE 0.5.0
+# DEPRECATED 1.4.0 (migrate to bugtraq instead)
+regex.global = true
+
+# Example global regex substitutions
+# Use !!! to separate the search pattern and the replace pattern
+# searchpattern!!!replacepattern
+# SINCE 0.5.0
+
+# regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://somehost/bug/$3">$3</a>
+# SINCE 0.5.0
+
+# Example Gerrit links
+# regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: <a href="http://somehost/r/#q,$2,n,z">$2</a>
+# regex.global.reviewedon = \\b(Reviewed-on:\\s*)([A-Za-z0-9:/\\.]*)\\b!!!Reviewed-on: <a href="$2">$2</a>
+
+# Example per-repository regex substitutions overrides global
+# SINCE 0.5.0
+# regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://elsewhere/bug/$3">$3</a>
+
+#
+# Mail Settings
+# SINCE 0.6.0
+#
+# Mail settings are used to notify administrators of received federation proposals
+#
+
+# ip or hostname of smtp server
+#
+# SINCE 0.6.0
+mail.server =
+
+# port to use for smtp requests
+#
+# SINCE 0.6.0
+mail.port = 25
+
+# debug the mail executor
+#
+# SINCE 0.6.0
+mail.debug = false
+
+# use SMTPs flag
+mail.smtps = false
+
+# use STARTTLS flag
+#
+# SINCE 1.6.0
+mail.starttls = false
+
+# if your smtp server requires authentication, supply the credentials here
+#
+# SINCE 0.6.0
+mail.username =
+# SINCE 0.6.0
+mail.password =
+
+# from address for generated emails
+#
+# SINCE 0.6.0
+mail.fromAddress = 
+
+# List of email addresses for the Gitblit administrators
+#
+# SPACE-DELIMITED
+# SINCE 0.6.0
+mail.adminAddresses = 
+
+# List of email addresses for sending push email notifications.
+#
+# This key currently requires use of the sendemail.groovy hook script.
+# If you set sendemail.groovy in *groovy.postReceiveScripts* then email
+# notifications for all repositories (regardless of access restrictions!)
+# will be sent to these addresses.
+#
+# SPACE-DELIMITED
+# SINCE 0.8.0
+mail.mailingLists =
+
+#
+# Federation Settings
+# SINCE 0.6.0
+#
+# A Gitblit federation is a way to backup one Gitblit instance to another.
+#
+# *git.enableGitServlet* must be true to use this feature.
+
+# Your federation name is used for federation status acknowledgments.  If it is
+# unset, and you elect to send a status acknowledgment, your Gitblit instance
+# will be identified by its hostname, if available, else your internal ip address.
+# The source Gitblit instance will also append your external IP address to your
+# identification to differentiate multiple pulling systems behind a single proxy.
+#
+# SINCE 0.6.0
+federation.name =
+
+# Specify the passphrase of this Gitblit instance.
+#
+# An unspecified (empty) passphrase disables processing federation requests.
+#
+# This value can be anything you want: an integer, a sentence, an haiku, etc.
+# Keep the value simple, though, to avoid Java properties file encoding issues.
+#
+# Changing your passphrase will break any registrations you have established with other
+# Gitblit instances.
+#
+# CASE-SENSITIVE
+# SINCE 0.6.0
+# RESTART REQUIRED *(only to enable or disable federation)*
+federation.passphrase =
+
+# Control whether or not this Gitblit instance can receive federation proposals
+# from another Gitblit instance.  Registering a federated Gitblit is a manual
+# process.  Proposals help to simplify that process by allowing a remote Gitblit
+# instance to send your Gitblit instance the federation pull data.
+#
+# SINCE 0.6.0
+federation.allowProposals = false
+
+# The destination folder for cached federation proposals.
+# Use forward slashes even on Windows!!
+#
+# SINCE 0.6.0
+# BASEFOLDER
+federation.proposalsFolder = ${baseFolder}/proposals
+
+# The default pull frequency if frequency is unspecified on a registration
+#
+# SINCE 0.6.0
+federation.defaultFrequency = 60 mins
+
+# Federation Sets are named groups of repositories.  The Federation Sets are 
+# available for selection in the repository settings page.  You can assign a
+# repository to one or more sets and then distribute the token for the set.
+# This allows you to grant federation pull access to a subset of your available
+# repositories.  Tokens for federation sets only grant repository pull access.
+#
+# SPACE-DELIMITED
+# CASE-SENSITIVE
+# SINCE 0.6.0
+federation.sets = 
+
+# Federation pull registrations
+# Registrations are read once, at startup.
+#
+# RESTART REQUIRED
+#
+# frequency:
+#   The shortest frequency allowed is every 5 minutes
+#   Decimal frequency values are cast to integers
+#   Frequency values may be specified in mins, hours, or days
+#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
+#
+# folder:
+#   if unspecified, the folder is *git.repositoriesFolder*
+#   if specified, the folder is relative to *git.repositoriesFolder*
+#
+# bare:
+#   if true, each repository will be created as a *bare* repository and will not
+#   have a working directory.
+#
+#   if false, each repository will be created as a normal repository suitable
+#   for local work.
+#
+# mirror:
+#   if true, each repository HEAD is reset to *origin/master* after each pull.
+#   The repository will be flagged *isFrozen* after the initial clone.
+#
+#   if false, each repository HEAD will point to the FETCH_HEAD of the initial
+#   clone from the origin until pushed to or otherwise manipulated.
+#
+# mergeAccounts:
+#   if true, remote accounts and their permissions are merged into your 
+#   users.properties file 
+#
+# notifyOnError:
+#   if true and the mail configuration is properly set, administrators will be
+#   notified by email of pull failures
+#
+# include and exclude:
+#   Space-delimited list of repositories to include or exclude from pull
+#   may be * wildcard to include or exclude all
+#   may use fuzzy match (e.g. org.eclipse.*)
+
+#
+# (Nearly) Perfect Mirror example
+#
+
+#federation.example1.url = https://go.gitblit.com
+#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
+#federation.example1.frequency = 120 mins
+#federation.example1.folder =
+#federation.example1.bare = true 
+#federation.example1.mirror = true 
+#federation.example1.mergeAccounts = true
+
+#
+# Advanced Realm Settings
+#
+
+# Auto-creates user accounts based on the servlet container principal.  This
+# assumes that your Gitblit install is a protected resource and your container's
+# authentication process intercepts all Gitblit requests.
+#
+# SINCE 1.3.0
+realm.container.autoCreateAccounts = false
+
+# A set of mapping used to map HTTP session attributes to user informations
+# They are used if realm.container.autoCreateAccounts is set to true and
+# the webapp container used can fill the session with user informations
+#
+# SINCE 1.7.0
+realm.container.autoAccounts.displayName = 
+realm.container.autoAccounts.emailAddress = 
+realm.container.autoAccounts.locale = 
+
+# If the user's created by the webapp container is given this role,
+# the user created will be a admin user.
+#
+# SINCE 1.7.0
+realm.container.autoAccounts.adminRole = 
+
+
+# Allow or prohibit Windows guest account logins
+#
+# SINCE 1.3.0
+realm.windows.allowGuests = false
+
+# Allow user accounts belonging to the BUILTIN\Administrators group to be
+# Gitblit administrators.
+#
+# SINCE 1.4.0
+realm.windows.permitBuiltInAdministrators = true
+
+# The default domain for authentication.
+#
+# If specified, this domain will be used for authentication UNLESS the supplied
+# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain)
+#
+# If unspecified, the username must be specified in UPN format (name@domain).
+#
+# if "." (dot) is specified, ONLY the local account database will be used.
+#
+# SINCE 1.3.0
+realm.windows.defaultDomain =
+
+# The PAM service name for authentication.
+# default: system-auth
+#
+# SINCE 1.3.1
+realm.pam.serviceName = system-auth
+
+# The Apache htpasswd file that contains the users and passwords.
+# default: ${baseFolder}/htpasswd
+#
+# RESTART REQUIRED
+# BASEFOLDER
+# SINCE 1.3.2
+realm.htpasswd.userfile = ${baseFolder}/htpasswd
+
+# Restrict the Salesforce user to members of this org.
+# default: 0 (i.e. do not check the Org ID)
+#
+# SINCE 1.3.0
+realm.salesforce.orgId = 0
+
+# URL of the LDAP server.
+# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to
+# send StartTLS command.
+#
+# SINCE 1.0.0
+realm.ldap.server = ldap://localhost
+
+# Login username for LDAP searches.
+# If this value is unspecified, anonymous LDAP login will be used.
+# 
+# e.g. mydomain\\username
+#
+# SINCE 1.0.0
+realm.ldap.username = cn=Directory Manager
+
+# Login password for LDAP searches.
+#
+# SINCE 1.0.0
+realm.ldap.password = password
+
+# Bind pattern for Authentication.
+# Allow to directly authenticate an user without LDAP Searches.
+# 
+# e.g. CN=${username},OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
+#
+# SINCE 1.5.0
+realm.ldap.bindpattern = 
+
+
+# Delegate team membership control to LDAP.
+#
+# If true, team user memberships will be specified by LDAP groups.  This will
+# disable team selection in Edit User and user selection in Edit Team.
+#
+# If false, LDAP will only be used for authentication and Gitblit will maintain
+# team memberships with the *realm.ldap.backingUserService*.
+#
+# SINCE 1.0.0
+realm.ldap.maintainTeams = false
+
+# Root node for all LDAP users
+#
+# This is the root node from which subtree user searches will begin.
+# If blank, Gitblit will search ALL nodes.
+#
+# SINCE 1.0.0
+realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
+
+# Filter criteria for LDAP users
+#
+# Query pattern to use when searching for a user account. This may be any valid 
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# Variables may be injected via the ${variableName} syntax.
+# Recognized variables are:
+#    ${username} - The text entered as the user name
+#
+# SINCE 1.0.0
+realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
+
+# Root node for all LDAP groups to be used as Gitblit Teams
+#
+# This is the root node from which subtree team searches will begin.
+# If blank, Gitblit will search ALL nodes.  
+#
+# SINCE 1.0.0
+realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain
+
+# Filter criteria for LDAP groups
+#
+# Query pattern to use when searching for a team. This may be any valid 
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# Variables may be injected via the ${variableName} syntax.
+# Recognized variables are:
+#    ${username} - The text entered as the user name
+#    ${dn} - The Distinguished Name of the user logged in
+#
+# All attributes from the LDAP User record are available. For example, if a user
+# has an attribute "fullName" set to "John", "(fn=${fullName})" will be 
+# translated to "(fn=John)".
+#
+# SINCE 1.0.0
+realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
+
+# Filter criteria for empty LDAP groups
+#
+# Query pattern to use when searching for an empty team. This may be any valid 
+# LDAP query expression, including the standard (&) and (|) operators.
+#
+# default: (&(objectClass=group)(!(member=*)))
+# SINCE 1.4.0
+realm.ldap.groupEmptyMemberPattern = (&(objectClass=group)(!(member=*)))
+
+# LDAP users or groups that should be given administrator privileges.
+#
+# Teams are specified with a leading '@' character.  Groups with spaces in the
+# name can be entered as "@team name".  This setting only applies when using
+# LDAP to maintain team memberships.
+#
+# e.g. realm.ldap.admins = john @git_admins "@git admins"
+#
+# SPACE-DELIMITED
+# SINCE 1.0.0
+realm.ldap.admins = @Git_Admins
+
+# Attribute(s) on the USER record that indicate their display (or full) name.
+# Leave blank for no mapping available in LDAP.
+#
+# This may be a single attribute, or a string of multiple attributes.  Examples:
+#  displayName - Uses the attribute 'displayName' on the user record
+#  ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3 
+#       attributes together, with a '.' after personalTitle
+#
+# SINCE 1.0.0
+realm.ldap.displayName = displayName
+
+# Attribute(s) on the USER record that indicate their email address.
+# Leave blank for no mapping available in LDAP.
+#
+# This may be a single attribute, or a string of multiple attributes.  Examples:
+#  email - Uses the attribute 'email' on the user record
+#  ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes
+#       together with a '.' and '@' creating something like first.last@gitblit.com 
+#
+# SINCE 1.0.0
+realm.ldap.email = email
+
+# Attribute on the USER record that indicate their username to be used in gitblit
+# when synchronizing users from LDAP
+# if blank, Gitblit will use uid
+# For MS Active Directory this may be sAMAccountName
+#
+# SINCE 1.0.0
+realm.ldap.uid = uid
+
+# Defines whether to synchronize all LDAP users and teams into the user service
+#
+# Valid values: true, false
+# If left blank, false is assumed
+#
+# SINCE 1.4.0
+realm.ldap.synchronize = false
+
+# Defines the period to be used when synchronizing users and teams from ldap.
+#
+# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS' 
+
+# default: 5 MINUTES
+#
+# RESTART REQUIRED
+# SINCE 1.4.0
+realm.ldap.syncPeriod = 5 MINUTES
+
+# Defines whether to delete non-existent LDAP users from the user service
+# during synchronization. depends on  realm.ldap.synchronize = true
+#
+# Valid values: true, false
+# If left blank, true is assumed
+#
+# SINCE 1.4.0
+realm.ldap.removeDeletedUsers = true
+
+# URL of the Redmine.
+#
+# SINCE 1.2.0
+realm.redmine.url = http://example.com/redmine
+
+#
+# Gitblit GO Server Settings
+# The following settings only affect the integrated GO variant.
+#
+
+# The temporary folder to decompress the embedded gitblit webapp. 
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+# BASEFOLDER
+server.tempFolder = ${baseFolder}/temp
+
+# Specify the maximum number of concurrent http/https Jetty worker
+# threads to allow.  This setting does not affect other threaded
+# daemons and components of Gitblit.
+#
+# SINCE 1.3.0
+# RESTART REQUIRED
+server.threadPoolSize = 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.
+#
+# SINCE 0.7.0
+# RESTART REQUIRED
+server.contextPath = /
+
+# Standard http port to serve.  <= 0 disables this connector.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 80 or 8080
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpPort = 0
+
+# Secure/SSL https port to serve. <= 0 disables this connector.
+# On Unix/Linux systems, ports < 1024 require root permissions.
+# Recommended value: 443 or 8443
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpsPort = 8443
+
+# Automatically redirect http requests to the secure https connector.
+#
+# This setting requires that you have configured server.httpPort and server.httpsPort.
+# Unless you are on a private LAN where you trust all client connections, it is
+# recommended to use https for all communications.
+#
+# SINCE 1.4.0
+# RESTART REQUIRED
+server.redirectToHttpsPort = false
+
+# Specify the interface for Jetty to bind the standard connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpBindInterface =
+
+# Specify the interface for Jetty to bind the secure connector.
+# You may specify an ip or an empty value to bind to all interfaces.
+# Specifying localhost will result in Gitblit ONLY listening to requests to
+# localhost.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.httpsBindInterface =
+
+# Alias of certificate to use for https/SSL serving.  If blank the first
+# certificate found in the keystore will be used. 
+#
+# SINCE 1.2.0
+# RESTART REQUIRED
+server.certificateAlias = localhost
+
+# Password for SSL keystore.
+# Keystore password and certificate password must match.
+# This is provided for convenience, its probably more secure to set this value
+# using the --storePassword command line parameter.
+#
+# If you are using the official JRE or JDK from Oracle you may not have the
+# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM.  Because
+# of this, your store/key password can not exceed 7 characters.  If you require
+# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction
+# Policy files from Oracle.
+#
+# http://www.oracle.com/technetwork/java/javase/downloads/index.html
+#
+# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited
+# Strength encryption is available.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.storePassword = gitblit
+
+# If serving over https (recommended) you might consider requiring clients to
+# authenticate with ssl certificates.  If enabled, only https clients with the
+# a valid client certificate will be able to access Gitblit.
+#
+# If disabled, client certificate authentication is optional and will be tried
+# first before falling-back to form authentication or basic authentication.
+#
+# Requiring client certificates to access any of Gitblit may be too extreme,
+# consider this carefully.
+#
+# SINCE 1.2.0
+# RESTART REQUIRED
+server.requireClientCertificates = false
+
+# Port for shutdown monitor to listen on.
+#
+# SINCE 0.5.0
+# RESTART REQUIRED
+server.shutdownPort = 8081
+
+#
+# Gitblit Filestore Settings
+#
+# The location to save the filestore blobs 
+#
+# SINCE 1.7.0
+filestore.storageFolder = ${baseFolder}/lfs
+
+# Maximum allowable upload size
+# The default value, -1, disables upload limits.
+# Common unit suffixes of k, m, or g are supported.
+# SINCE 1.7.0
+filestore.maxUploadSize = -1
diff --git a/src/main/distrib/data/gitblit.properties b/src/main/distrib/data/gitblit.properties
index f53cc11..a4202e0 100644
--- a/src/main/distrib/data/gitblit.properties
+++ b/src/main/distrib/data/gitblit.properties
@@ -1,1891 +1,21 @@
 #
-# Gitblit Settings
+# GITBLIT.PROPERTIES
 #
-
-# This settings file supports parameterization from the command-line for the
-# following command-line parameters:
-#
-#   --baseFolder    ${baseFolder}    SINCE 1.2.1
-#
-# Settings that support ${baseFolder} parameter substitution are indicated with the
-# BASEFOLDER attribute.  If the --baseFolder argument is unspecified, ${baseFolder}
-# and it's trailing / will be discarded from the setting value leaving a relative
-# path that is equivalent to pre-1.2.1 releases.
-#
-# e.g. "${baseFolder}/git" becomes "git", if --baseFolder is unspecified 
-#
-# Git Servlet Settings
+# Define your custom settings in this file and/or include settings defined in
+# other properties files.
 #
 
-# Base folder for repositories.
-# This folder may contain bare and non-bare repositories but Gitblit will only
-# allow you to push to bare repositories.
-# Use forward slashes even on Windows!!
-# e.g. c:/gitrepos
+# Include Gitblit's 'defaults.properties' within your configuration.
 #
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-git.repositoriesFolder = ${baseFolder}/git
-
-# Build the available repository list at startup and cache this list for reuse.
-# This reduces disk io when presenting the repositories page, responding to rpcs,
-# etc, but it means that  Gitblit will not automatically identify repositories
-# added or deleted by external tools.
+# NOTE: Gitblit will not automatically reload "included" properties.  Gitblit
+# only watches the 'gitblit.properties' file for modifications.
 #
-# For this case you can use curl, wget, etc to issue an rpc request to clear the
-# cache (e.g. https://localhost/rpc?req=CLEAR_REPOSITORY_CACHE)
+# Paths may be relative to the ${baseFolder} or they may be absolute.
 #
-# SINCE 1.1.0
-git.cacheRepositoryList = true
+# COMMA-DELIMITED
+# SINCE 1.7.0
+include = defaults.properties
 
-# Search the repositories folder subfolders for other repositories.
-# Repositories MAY NOT be nested (i.e. one repository within another)
-# but they may be grouped together in subfolders.
-# e.g. c:/gitrepos/libraries/mylibrary.git
-#      c:/gitrepos/libraries/myotherlibrary.git
 #
-# SINCE 0.5.0
-git.searchRepositoriesSubfolders = true
-
-# Maximum number of folders to recurse into when searching for repositories.
-# The default value, -1, disables depth limits.
-#
-# SINCE 1.1.0
-git.searchRecursionDepth = -1
-
-# List of regex exclusion patterns to match against folders found in
-# *git.repositoriesFolder*.
-# Use forward slashes even on Windows!!
-# e.g. test/jgit\.git
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.1.0
-git.searchExclusions =
-
-# List of regex url patterns for extracting a repository name when locating
-# submodules.
-#   e.g. git.submoduleUrlPatterns = .*?://github.com/(.*) will extract
-#   *gitblit/gitblit.git* from *git://github.com/gitblit/gitblit.git*
-# If no matches are found then the submodule repository name is assumed to be
-# whatever trails the last / character. (e.g. gitblit.git).
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.1.0
-git.submoduleUrlPatterns = .*?://github.com/(.*)
-
-# Specify the interface for Git Daemon to bind it's service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-git.daemonBindInterface = 
-
-# port for serving the Git Daemon service.  <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 9418
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-git.daemonPort = 9418
-
-# The port for serving the SSH service.  <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 29418
-#
-# SINCE 1.5.0
-# RESTART REQUIRED
-git.sshPort = 29418
-
-# Specify the interface for the SSH daemon to bind its service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.5.0
-# RESTART REQUIRED
-git.sshBindInterface = 
-
-# Specify the SSH key manager to use for retrieving, storing, and removing
-# SSH keys.
-#
-# Valid key managers are:
-#    com.gitblit.transport.ssh.FileKeyManager
-#
-# SINCE 1.5.0
-git.sshKeysManager = com.gitblit.transport.ssh.FileKeyManager
-
-# Directory for storing user SSH keys when using the FileKeyManager.
-#
-# SINCE 1.5.0
-git.sshKeysFolder= ${baseFolder}/ssh
-
-# SSH backend NIO2|MINA.
-#
-# The Apache Mina project recommends using the NIO2 backend.
-#
-# SINCE 1.5.0
-git.sshBackend = NIO2
-
-# Number of threads used to parse a command line submitted by a client over SSH
-# for execution, create the internal data structures used by that command,
-# and schedule it for execution on another thread.
-#
-# SINCE 1.5.0
-git.sshCommandStartThreads = 2
-
-
-# Allow push/pull over http/https with JGit servlet.
-# If you do NOT want to allow Git clients to clone/push to Gitblit set this
-# to false.  You might want to do this if you are only using ssh:// or git://.
-# If you set this false, consider changing the *web.otherUrls* setting to
-# indicate your clone/push urls.
-#
-# SINCE 0.5.0
-git.enableGitServlet = true
-
-# If you want to restrict all git servlet access to those with valid X509 client
-# certificates then set this value to true.
-#
-# SINCE 1.2.0
-git.requiresClientCertificate = false
-
-# Enforce date checks on client certificates to ensure that they are not being
-# used prematurely and that they have not expired.
-#
-# SINCE 1.2.0
-git.enforceCertificateValidity = true
-
-# List of OIDs to extract from a client certificate DN to map a certificate to
-# an account username.
-#
-# e.g. git.certificateUsernameOIDs = CN
-# e.g. git.certificateUsernameOIDs = FirstName LastName
-#
-# SPACE-DELIMITED
-# SINCE 1.2.0
-git.certificateUsernameOIDs = CN
-
-# Only serve/display bare repositories.
-# If there are non-bare repositories in git.repositoriesFolder and this setting
-# is true, they will be excluded from the ui. 
-#
-# SINCE 0.9.0
-git.onlyAccessBareRepositories = false
-
-
-# Specify the list of acceptable transports for pushes.
-# If this setting is empty, all transports are acceptable.
-#
-# Valid choices are: GIT HTTP HTTPS SSH
-#
-# SINCE 1.5.0
-# SPACE-DELIMITED
-git.acceptedPushTransports = HTTP HTTPS SSH
-
-# Allow an authenticated user to create a destination repository on a push if
-# the repository does not already exist.
-#
-# Administrator accounts can create a repository in any project.
-# These repositories are created with the default access restriction and authorization
-# control values.  The pushing account is set as the owner.
-#
-# Non-administrator accounts with the CREATE role may create personal repositories.
-# These repositories are created as VIEW restricted for NAMED users.
-# The pushing account is set as the owner.
-#
-# SINCE 1.2.0
-git.allowCreateOnPush = true
-
-# Global setting to control anonymous pushes.
-#
-# This setting allows/rejects anonymous pushes at the level of the receive pack.
-# This trumps all repository config settings.  While anonymous pushes are convenient
-# on your own box when you are a lone developer,  they are not recommended for
-# any multi-user installation where accountability is required.  Since Gitblit
-# tracks pushes and user accounts, allowing anonymous pushes compromises that
-# information.
-#
-# SINCE 1.4.0
-git.allowAnonymousPushes = false
-
-# The default access restriction for new repositories.
-# Valid values are NONE, PUSH, CLONE, VIEW
-#  NONE = anonymous view, clone, & push
-#  PUSH = anonymous view & clone and authenticated push
-#  CLONE = anonymous view, authenticated clone & push
-#  VIEW = authenticated view, clone, & push
-#
-# SINCE 1.0.0
-git.defaultAccessRestriction = PUSH
-
-# The default authorization control for new repositories.
-# Valid values are AUTHENTICATED and NAMED
-#  AUTHENTICATED = any authenticated user is granted restricted access
-#  NAMED = only named users/teams are granted restricted access
-#
-# SINCE 1.1.0
-git.defaultAuthorizationControl = NAMED
-
-# The prefix for a users personal repository directory.
-#
-# Personal user repositories are created in this directory, named by the user name
-# prefixed with the userRepositoryPrefix. For eaxmple, a user 'john' would have his
-# personal repositories in the directory '~john'.
-#
-# Cannot be an empty string. Also, absolute paths are changed to relative paths by 
-# removing the first directory separator.
-#
-# It is not recommended to change this value AFTER your user's have created
-# personal repositories because it will break all permissions, ownership, and
-# repository push/pull operations. 
-#
-# RESTART REQUIRED
-# SINCE 1.4.0
-git.userRepositoryPrefix = ~
-
-# The default incremental push tag prefix.  Tag prefix applied to a repository
-# that has automatic push tags enabled and does not specify a custom tag prefix.
-#
-# If incremental push tags are enabled, the tips of each branch in the push will
-# be tagged with an increasing revision integer.
-#
-# e.g. refs/tags/r2345 or refs/tags/rev_2345 
-#
-# SINCE 1.3.0
-git.defaultIncrementalPushTagPrefix = r
-
-# Controls creating a repository as --shared on Unix servers.
-#
-# In an Unix environment where mixed access methods exist for shared repositories,
-# the repository should be created with 'git init --shared' to make sure that
-# it can be accessed e.g. via ssh (user git) and http (user www-data).
-#
-# Valid values are the values available for the '--shared' option. The the manual
-# page for 'git init' for more information on shared repositories.
-#
-# SINCE 1.4.0
-git.createRepositoriesShared = false
-
-# Directory for gitignore templates used during repository creation.
-#
-# SINCE 1.6.0
-git.gitignoreFolder = ${baseFolder}/gitignore
-
-# Enable JGit-based garbage collection. (!!EXPERIMENTAL!!)
-#
-# USE AT YOUR OWN RISK!
-#
-# If enabled, the garbage collection executor scans all repositories once a day
-# at the hour of your choosing.  The GC executor will take each repository "offline",
-# one-at-a-time, to check if the repository satisfies it's GC trigger requirements.
-#
-# While the repository is offline it will be inaccessible from the web UI or from
-# any of the other services (git, rpc, rss, etc).
-#
-# Gitblit's GC Executor MAY NOT PLAY NICE with the other Git kids on the block,
-# especially on Windows systems, so if you are using other tools please coordinate
-# their usage with your GC Executor schedule or do not use this feature.
-#
-# The GC algorithm complex and the JGit team advises caution when using their
-# young implementation of GC.
-#
-# http://wiki.eclipse.org/EGit/New_and_Noteworthy/2.1#Garbage_Collector_and_Repository_Storage_Statistics
-#
-# EXPERIMENTAL
-# SINCE 1.2.0
-# RESTART REQUIRED
-git.enableGarbageCollection = false
-
-# Hour of the day for the GC Executor to scan repositories.
-# This value is in 24-hour time.
-#
-# SINCE 1.2.0
-git.garbageCollectionHour = 0
-
-# The default minimum total filesize of loose objects to trigger early garbage
-# collection.
-#
-# You may specify a custom threshold for a repository in the repository's settings.
-# Common unit suffixes of k, m, or g are supported.
-#
-# SINCE 1.2.0
-git.defaultGarbageCollectionThreshold = 500k
-
-# The default period, in days, between GCs for a repository.  If the total filesize
-# of the loose object exceeds *git.garbageCollectionThreshold* or the repository's
-# custom threshold, this period will be short-circuited. 
-#
-# e.g. if a repository collects 100KB of loose objects every day with a 500KB
-# threshold and a period of 7 days, it will take 5 days for the loose objects to
-# be collected, packed, and pruned.
-#
-# OR
-#
-# if a repository collects 10KB of loose objects every day with a 500KB threshold
-# and a period of 7 days, it will take the full 7 days for the loose objects to be
-# collected, packed, and pruned.
-#
-# You may specify a custom period for a repository in the repository's settings.
-#
-# The minimum value is 1 day since the GC Executor only runs once a day.
-#
-# SINCE 1.2.0
-git.defaultGarbageCollectionPeriod = 7
-
-# Gitblit can automatically fetch ref updates for a properly configured mirror
-# repository.
-#
-# Requirements:
-# 1. you must manually clone the repository using native git
-#    git clone --mirror git://somewhere.com/myrepo.git
-# 2. the "origin" remote must be the mirror source
-# 3. the "origin" repository must be accessible without authentication OR the
-#    credentials must be embedded in the origin url (not recommended)
-#
-# Notes:
-# 1. "origin" SSH urls are untested and not likely to work
-# 2. mirrors cloned while Gitblit is running are likely to require clearing the
-#    gitblit cache (link on the repositories page of an administrator account)
-# 3. Gitblit will automatically repair any invalid fetch refspecs with a "//"
-#    sequence.
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-git.enableMirroring = false
-
-# Specify the period between update checks for mirrored repositories.
-# The shortest period you may specify between mirror update checks is 5 mins.
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-git.mirrorPeriod = 30 mins
-
-# Number of bytes of a pack file to load into memory in a single read operation.
-# This is the "page size" of the JGit buffer cache, used for all pack access
-# operations. All disk IO occurs as single window reads. Setting this too large
-# may cause the process to load more data than is required; setting this too small
-# may increase the frequency of read() system calls.
-#
-# Default on JGit is 8 KiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitWindowSize = 8k
-
-# Maximum number of bytes to load and cache in memory from pack files. If JGit
-# needs to access more than this many bytes it will unload less frequently used
-# windows to reclaim memory space within the process. As this buffer must be shared
-# with the rest of the JVM heap, it should be a fraction of the total memory available.
-#
-# The JGit team recommends setting this value larger than the size of your biggest
-# repository. This ensures you can serve most requests from memory.
-#
-# Default on JGit is 10 MiB on all platforms.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitLimit = 10m
-
-# Maximum number of bytes to reserve for caching base objects that multiple deltafied
-# objects reference. By storing the entire decompressed base object in a cache Git
-# is able to avoid unpacking and decompressing frequently used base objects multiple times.
-#
-# Default on JGit is 10 MiB on all platforms. You probably do not need to adjust
-# this value.
-#
-# Common unit suffixes of k, m, or g are supported.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.deltaBaseCacheLimit = 10m
-
-# Maximum number of pack files to have open at once. A pack file must be opened
-# in order for any of its data to be available in a cached window.
-#
-# If you increase this to a larger setting you may need to also adjust the ulimit
-# on file descriptors for the host JVM, as Gitblit needs additional file descriptors
-# available for network sockets and other repository data manipulation.
-#
-# Default on JGit is 128 file descriptors on all platforms.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitOpenFiles = 128
-
-# When true, JGit will use mmap() rather than malloc()+read() to load data from
-# pack files.  The use of mmap can be problematic on some JVMs as the garbage
-# collector must deduce that a memory mapped segment is no longer in use before
-# a call to munmap() can be made by the JVM native code.
-#
-# In server applications (such as Gitblit) that need to access many pack files,
-# setting this to true risks artificially running out of virtual address space, 
-# as the garbage collector cannot reclaim unused mapped spaces fast enough.
-#
-# Default on JGit is false. Although potentially slower, it yields much more
-# predictable behavior.
-# Documentation courtesy of the Gerrit project.
-#
-# SINCE 1.0.0
-# RESTART REQUIRED
-git.packedGitMmap = false
-
-# Validate all received (pushed) objects are valid.
-#
-# SINCE 1.5.0
-git.checkReceivedObjects = true
-
-# Validate all referenced but not supplied objects are reachable.
-#
-# If enabled, Gitblit will verify that references to objects not contained
-# within the received pack are already reachable through at least one other
-# reference advertised to clients.
-#
-# This feature is useful when Gitblit doesn't trust the client to not provide a
-# forged SHA-1 reference to an object, in an attempt to access parts of the DAG
-# that they aren't allowed to see and which have been hidden from them via the
-# configured AdvertiseRefsHook or RefFilter.
-#
-# Enabling this feature may imply at least some, if not all, of the same functionality
-# performed by git.checkReceivedObjects. 
-#
-# SINCE 1.5.0
-git.checkReferencedObjectsAreReachable = true
-
-# Set the maximum allowed Git object size.
-#
-# If an object is larger than the given size the pack-parsing will throw an exception
-# aborting the receive-pack operation.  The default value, 0, disables maximum
-# object size checking.
-#
-# SINCE 1.5.0
-git.maxObjectSizeLimit = 0
-
-# Set the maximum allowed pack size.
-#
-# A pack exceeding this size will be rejected. The default value, -1, disables
-# maximum pack size checking.
-#
-# SINCE 1.5.0
-git.maxPackSizeLimit = -1
-
-# Use the Gitblit patch receive pack for processing contributions and tickets.
-# This allows the user to push a patch using the familiar Gerrit syntax:
-#
-#    git push <remote> HEAD:refs/for/<targetBranch>
-#
-# NOTE:
-# This requires git.enableGitServlet = true AND it requires an authenticated
-# git transport connection (http/https) when pushing from a client.
-#
-# Valid services include:
-#    com.gitblit.tickets.FileTicketService
-#    com.gitblit.tickets.BranchTicketService
-#    com.gitblit.tickets.RedisTicketService
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-tickets.service = 
-
-# Globally enable or disable creation of new bug, enhancement, task, etc tickets
-# for all repositories.
-#
-# If false, no tickets can be created through the ui for any repositories.
-# If true, each repository can control if they allow new tickets to be created.
-#
-# NOTE:
-# If a repository is accepting patchsets, new proposal tickets can be created
-# regardless of this setting.
-#
-# SINCE 1.4.0
-tickets.acceptNewTickets = true
-
-# Globally enable or disable pushing patchsets to all repositories.
-#
-# If false, no patchsets will be accepted for any repositories.
-# If true, each repository can control if they accept new patchsets.
-#
-# NOTE:
-# If a repository is accepting patchsets, new proposal tickets can be created
-# regardless of the acceptNewTickets setting.
-#
-# SINCE 1.4.0
-tickets.acceptNewPatchsets = true
-
-# Default setting to control patchset merge through the web ui.  If true, patchsets
-# must have an approval score to enable the merge button.  This setting can be
-# overriden per-repository.
-#
-# SINCE 1.4.0
-tickets.requireApproval = false
-
-# The case-insensitive regular expression used to identify and close tickets on
-# push to the integration branch for commits that are NOT already referenced as
-# a patchset tip.
-#
-# SINCE 1.5.0
-tickets.closeOnPushCommitMessageRegex = (?:fixes|closes)[\\s-]+#?(\\d+)
-
-# Specify the location of the Lucene Ticket index
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-tickets.indexFolder = ${baseFolder}/tickets/lucene
-
-# Define the url for the Redis server.
-#
-# e.g. redis://localhost:6379
-#      redis://:foobared@localhost:6379/2
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-tickets.redis.url =
-
-# The number of tickets to display on a page.
-#
-# SINCE 1.4.0
-tickets.perPage = 25
-
-# The folder where plugins are loaded from.
-#
-# SINCE 1.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-plugins.folder = ${baseFolder}/plugins
-
-# The registry of available plugins.
-#
-# SINCE 1.5.0
-plugins.registry = http://plugins.gitblit.com/plugins.json
-
-# Number of threads used to handle miscellaneous tasks in the background.
-#
-# SINCE 1.6.0
-# RESTART REQUIRED
-execution.defaultThreadPoolSize = 1
-
-#
-# Groovy Integration
-#
-
-# Location of Groovy scripts to use for Pre and Post receive hooks.
-# Use forward slashes even on Windows!!
-# e.g. c:/groovy
-#
-# RESTART REQUIRED
-# SINCE 0.8.0
-# BASEFOLDER
-groovy.scriptsFolder = ${baseFolder}/groovy
-
-# Specify the directory Grape uses for downloading libraries.
-# http://groovy.codehaus.org/Grape
-#
-# RESTART REQUIRED
-# SINCE 1.0.0
-# BASEFOLDER
-groovy.grapeFolder = ${baseFolder}/groovy/grape
-
-# Scripts to execute on Pre-Receive.
-#
-# These scripts execute after an incoming push has been parsed and validated
-# but BEFORE the changes are applied to the repository.  You might reject a
-# push in this script based on the repository and branch the push is attempting
-# to change.
-#
-# Script names are case-sensitive on case-sensitive file systems.  You may omit
-# the traditional ".groovy" from this list if your file extension is ".groovy" 
-#
-# NOTE:
-# These scripts are only executed when pushing to *Gitblit*, not to other Git
-# tooling you may be using.  Also note that these scripts are shared between
-# repositories. These are NOT repository-specific scripts!  Within the script
-# you may customize the control-flow for a specific repository by checking the
-# *repository* variable.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.8.0
-groovy.preReceiveScripts =
-
-# Scripts to execute on Post-Receive.
-#
-# These scripts execute AFTER an incoming push has been applied to a repository.
-# You might trigger a continuous-integration build here or send a notification.
-#
-# Script names are case-sensitive on case-sensitive file systems.  You may omit
-# the traditional ".groovy" from this list if your file extension is ".groovy" 
-#
-# NOTE:
-# These scripts are only executed when pushing to *Gitblit*, not to other Git
-# tooling you may be using.  Also note that these scripts are shared between
-# repositories. These are NOT repository-specific scripts!  Within the script
-# you may customize the control-flow for a specific repository by checking the
-# *repository* variable.
-# 
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.8.0
-groovy.postReceiveScripts =
-
-# Repository custom fields for Groovy Hook mechanism
-#
-# List of key=label pairs of custom fields to prompt for in the Edit Repository
-# page.  These keys are stored in the repository's git config file in the 
-# section [gitblit "customFields"].  Key names are alphanumeric only.  These
-# fields are intended to be used for the Groovy hook mechanism where a script
-# can adjust it's execution based on the custom fields stored in the repository
-# config.
-#
-# e.g. "commitMsgRegex=Commit Message Regular Expression" anotherProperty=Another
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-groovy.customFields = 
-
-#
-# Fanout Settings
-#
-
-# Fanout is a PubSub notification service that can be used by Sparkleshare
-# to eliminate repository change polling.  The fanout service runs in a separate
-# thread on a separate port from the Gitblit http/https application.
-# This service is provided so that Sparkleshare may be used with Gitblit in
-# firewalled environments or where reliance on Sparkleshare's default notifications
-# server (notifications.sparkleshare.org) is unwanted.
-#
-# This service maintains an open socket connection from the client to the
-# Fanout PubSub service. This service may not work properly behind a proxy server.  
-
-# Specify the interface for Fanout to bind it's service.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.bindInterface = 
-
-# port for serving the Fanout PubSub service.  <= 0 disables this service.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 17000
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.port = 0
-
-# Use Fanout NIO service.  If false, a multi-threaded socket service will be used.
-# Be advised, the socket implementation spawns a thread per connection plus the
-# connection acceptor thread.  The NIO implementation is completely single-threaded.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.useNio = true
-
-# Concurrent connection limit.  <= 0 disables concurrent connection throttling.
-# If > 0, only the specified number of concurrent connections will be allowed
-# and all other connections will be rejected.
-#
-# SINCE 1.2.1
-# RESTART REQUIRED
-fanout.connectionLimit = 0
-
-#
-# Authentication Settings
-#
-
-# Require authentication to see everything but the admin pages
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.authenticateViewPages = false
-
-# If web.authenticateViewPages=true you may optionally require a client-side
-# basic authentication prompt instead of the standard form-based login. 
-#
-# SINCE 1.3.0
-web.enforceHttpBasicAuthentication = false
-
-# Require admin authentication for the admin functions and pages
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.authenticateAdminPages = true
-
-# Allow Gitblit to store a cookie in the user's browser for automatic
-# authentication.  The cookie is generated by the user service.
-#
-# SINCE 0.5.0
-web.allowCookieAuthentication = true
-
-# Allow deletion of non-empty repositories. This is enforced for all delete vectors.
-#
-# SINCE 1.6.0
-web.allowDeletingNonEmptyRepositories = true
-
-# Setting to include personal repositories in the main repositories list.
-#
-# SINCE 1.6.0
-web.includePersonalRepositories = false
-
-# Config file for storing project metadata
-#
-# SINCE 1.2.0
-# BASEFOLDER
-web.projectsFile = ${baseFolder}/projects.conf
-
-# Either the full path to a user config file (users.conf)
-# OR a fully qualified class name that implements the IUserService interface.
-#
-# Any custom user service implementation must have a public default constructor.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-realm.userService = ${baseFolder}/users.conf
-
-# Ordered list of external authentication providers which will be used if
-# authentication against the local user service fails.
-#
-# Valid providers are:
-#
-#    htpasswd
-#    ldap
-#    pam
-#    redmine
-#    salesforce
-#    windows
-
-# e.g. realm.authenticationProviders = htpasswd windows
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-# SPACE-DELIMITED
-realm.authenticationProviders =
-
-# How to store passwords.
-# Valid values are plain, md5, or combined-md5.  md5 is the hash of password.
-# combined-md5 is the hash of username.toLowerCase()+password.
-# Default is md5.
-#
-# SINCE 0.5.0 
-realm.passwordStorage = md5
-
-# Minimum valid length for a plain text password.
-# Default value is 5.  Absolute minimum is 4.
-#
-# SINCE 0.5.0 
-realm.minPasswordLength = 5
-
-#
-# Gitblit Web Settings
-#
-# If blank Gitblit is displayed.
-#
-# SINCE 0.5.0
-web.siteName =
-
-# 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 = 
-
-# You may specify a different logo image for the header but it must be 120x45px.
-# If the specified file does not exist, the default Gitblit logo will be used.
-#
-# SINCE 1.3.0
-# BASEFOLDER
-web.headerLogo = ${baseFolder}/logo.png
-
-# You may specify a different link URL for the logo image anchor.
-# If blank the Gitblit main page URL is used.
-#
-# SINCE 1.3.0
-# BASEFOLDER
-web.rootLink =
-
-# You may specify a custom header background CSS color.  If unspecified, the
-# default color will be used.
-#
-# e.g. web.headerBackgroundColor = #002060
-#
-# SINCE 1.3.0
-web.headerBackgroundColor =
-
-# You may specify a custom header foreground CSS color.  If unspecified, the
-# default color will be used.
-#
-# e.g. web.headerForegroundColor = white
-#
-# SINCE 1.3.0
-web.headerForegroundColor =
-
-# You may specify a custom header foreground hover CSS color.  If unspecified, the
-# default color will be used.
-#
-# e.g. web.headerHoverColor = white
-#
-# SINCE 1.3.0
-web.headerHoverColor =
-
-# You may specify a custom header border CSS color.  If unspecified, the default
-# color will be used.
-#
-# e.g. web.headerBorderColor = #002060
-#
-# SINCE 1.3.0
-web.headerBorderColor =
-
-# You may specify a custom header border CSS color.  If unspecified, the default
-# color will be used.
-#
-# e.g. web.headerBorderFocusColor = #ff9900
-#
-# SINCE 1.3.0
-web.headerBorderFocusColor =
-
-# If *web.authenticateAdminPages*=true, users with "admin" role can create
-# repositories, create users, and edit repository metadata.
-#
-# If *web.authenticateAdminPages*=false, any user can execute the aforementioned
-# functions. 
-#
-# SINCE 0.5.0 
-web.allowAdministration = true
-
-# Setting to disable rendering the top-level navigation header which includes
-# the login form, top-level links like dashboard, repositories, search, etc.
-# This setting is only useful if you plan to embed Gitblit within another page
-# or system.
-#
-# SINCE 1.4.0
-web.hideHeader = false
-
-# Allows rpc clients to list repositories and possibly manage or administer the 
-# Gitblit server, if the authenticated account has administrator permissions.
-# See *web.enableRpcManagement* and *web.enableRpcAdministration*.
-#
-# SINCE 0.7.0 
-web.enableRpcServlet = true
-
-# Allows rpc clients to manage repositories and users of the Gitblit instance,
-# if the authenticated account has administrator permissions.
-# Requires *web.enableRpcServlet=true*.
-#
-# SINCE 0.7.0 
-web.enableRpcManagement = false
-
-# Allows rpc clients to control the server settings and monitor the health of this
-# this Gitblit instance, if the authenticated account has administrator permissions.
-# Requires *web.enableRpcServlet=true* and *web.enableRpcManagement*.
-#
-# SINCE 0.7.0 
-web.enableRpcAdministration = false
-
-# Full path to a configurable robots.txt file.  With this file you can control
-# what parts of your Gitblit server respectable robots are allowed to traverse.
-# http://googlewebmastercentral.blogspot.com/2008/06/improving-on-robots-exclusion-protocol.html
-#
-# SINCE 1.0.0
-# BASEFOLDER
-web.robots.txt = ${baseFolder}/robots.txt
-
-# The number of minutes to cache a page in the browser since the last request.
-# The default value is 0 minutes.  A value <= 0 disables all page caching which
-# is the default behavior for Gitblit <= 1.3.0.
-#
-# SINCE 1.3.1
-web.pageCacheExpires = 0
-
-# If true, the web ui layout will respond and adapt to the browser's dimensions.
-# if false, the web ui will use a 940px fixed-width layout.
-# http://twitter.github.com/bootstrap/scaffolding.html#responsive
-#
-# SINCE 1.0.0
-web.useResponsiveLayout = true
-
-# Allow Gravatar images to be displayed in Gitblit pages.
-#
-# SINCE 0.8.0
-web.allowGravatar = true
-
-# Allow dynamic zip downloads.
-#
-# SINCE 0.5.0   
-web.allowZipDownloads = true
-
-# If *web.allowZipDownloads=true* the following formats will be displayed for
-# download compressed archive links:
-#
-# zip   = standard .zip
-# tar   = standard tar format (preserves *nix permissions and symlinks)
-# gz    = gz-compressed tar
-# xz    = xz-compressed tar
-# bzip2 = bzip2-compressed tar
-#
-# SPACE-DELIMITED
-# SINCE 1.2.0
-web.compressedDownloads = zip gz
-
-# Allow optional Lucene integration. Lucene indexing is an opt-in feature.
-# A repository may specify branches to index with Lucene instead of using Git
-# commit traversal. There are scenarios where you may want to completely disable
-# Lucene indexing despite a repository specifying indexed branches.  One such
-# scenario is on a resource-constrained federated Gitblit mirror.
-#
-# SINCE 0.9.0
-web.allowLuceneIndexing = true
-
-# Control the frequency of Lucene repository indexing.
-# The default setting is to check for updated refs every 2 mins.
-#
-# SINCE 1.6.1
-web.luceneFrequency = 2 mins
-
-# Allows an authenticated user to create forks of a repository
-#
-# set this to false if you want to disable all fork controls on the web site
-#
-web.allowForking = true
-
-# Controls the length of shortened commit hash ids
-#
-# SINCE 1.2.0
-web.shortCommitIdLength = 6
-
-# Use Clippy (Flash solution) to provide a copy-to-clipboard button.
-# If false, a button with a more primitive JavaScript-based prompt box will
-# offer a 3-step (click, ctrl+c, enter) copy-to-clipboard alternative.
-#
-# SINCE 0.8.0
-web.allowFlashCopyToClipboard = true
-
-# Default maximum number of commits that a repository may contribute to the
-# activity page, regardless of the selected duration.  This setting may be valuable
-# for an extremely busy server.  This value may also be configed per-repository
-# in Edit Repository. 0 disables this throttle.
-#
-# SINCE 1.2.0
-web.maxActivityCommits = 0
-
-# Default number of entries to include in RSS Syndication links
-#
-# SINCE 0.5.0
-web.syndicationEntries = 25
-
-# Show the size of each repository on the repositories page.
-# This requires recursive traversal of each repository folder.  This may be
-# non-performant on some operating systems and/or filesystems. 
-#
-# SINCE 0.5.2
-web.showRepositorySizes = true
-
-# List of custom regex expressions that can be displayed in the Filters menu
-# of the Repositories and Activity pages.  Keep them very simple because you
-# are likely to run into encoding issues if they are too complex.
-#
-# Use !!! to separate the filters 
-#
-# SINCE 0.8.0
-web.customFilters =
-
-# Show federation registrations (without token) and the current pull status
-# to non-administrator users. 
-#
-# SINCE 0.6.0
-web.showFederationRegistrations = false
-
-# This is the message displayed when *web.authenticateViewPages=true*.
-# This can point to a file with Markdown content.
-# Specifying "gitblit" uses the internal login message.
-#
-# SINCE 0.7.0
-# BASEFOLDER
-web.loginMessage = gitblit
-
-# This is the message displayed above the repositories table.
-# This can point to a file with Markdown content.
-# Specifying "gitblit" uses the internal welcome message.
-#
-# SINCE 0.5.0
-# BASEFOLDER
-web.repositoriesMessage = gitblit
-
-# Ordered list of charsets/encodings to use when trying to display a blob.
-# If empty, UTF-8 and ISO-8859-1 are used.  The server's default charset
-# is always appended to the encoding list.  If all encodings fail to cleanly
-# decode the blob content, UTF-8 will be used with the standard malformed
-# input/unmappable character replacement strings.
-# 
-# SPACE-DELIMITED
-# SINCE 1.0.0
-web.blobEncodings = UTF-8 ISO-8859-1
-
-# Manually set the default timezone to be used by Gitblit for display in the 
-# web ui.  This value is independent of the JVM timezone.  Specifying a blank
-# value will default to the JVM timezone.
-# e.g. America/New_York, US/Pacific, UTC, Europe/Berlin
-#
-# SINCE 0.9.0
-# RESTART REQUIRED
-web.timezone =
-
-# Use the client timezone when formatting dates.
-# This uses AJAX to determine the browser's timezone and may require more
-# server overhead because a Wicket session is created.  All Gitblit pages
-# attempt to be stateless, if possible.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.useClientTimezone = false
-
-# Time format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.8.0
-web.timeFormat = HH:mm
-
-# Short date format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.5.0
-web.datestampShortFormat = yyyy-MM-dd
-
-# Long date format
-#
-# SINCE 0.8.0
-web.datestampLongFormat = EEEE, MMMM d, yyyy
-
-# Long timestamp format
-# <http://download.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html>
-#
-# SINCE 0.5.0
-web.datetimestampLongFormat = EEEE, MMMM d, yyyy HH:mm Z
-
-# Mount URL parameters
-# This setting controls if pretty or parameter URLs are used.
-# i.e.
-# if true:
-#     http://localhost/commit/myrepo/abcdef
-# if false:
-#     http://localhost/commit/?r=myrepo&h=abcdef
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.mountParameters = true
-
-# Some servlet containers (e.g. Tomcat >= 6.0.10) disallow '/' (%2F) encoding
-# in URLs as a security precaution for proxies.  This setting tells Gitblit
-# to preemptively replace '/' with '*' or '!' for url string parameters.
-#
-# <https://issues.apache.org/jira/browse/WICKET-1303>
-# <http://tomcat.apache.org/security-6.html#Fixed_in_Apache_Tomcat_6.0.10>
-# Add *-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true* to your
-# *CATALINA_OPTS* or to your JVM launch parameters
-#
-# SINCE 0.5.2
-web.forwardSlashCharacter = /
-
-# Show other URLs on the summary page for accessing your git repositories
-# Use spaces to separate urls.
-#
-# {0} is the token for the repository name
-# {1} is the token for the username
-#
-# The username is only practical if you have setup your other git serving
-# solutions accounts to have the same username as the Gitblit account.
-#
-# e.g.
-# web.otherUrls = ssh://localhost/git/{0} git://localhost/git/{0} https://{1}@localhost/r/{0}
-#
-# SPACE-DELIMITED
-# 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)
-#
-# SINCE 0.5.0
-web.repositoryListType = grouped
-
-# If using a grouped repository list and there are repositories at the
-# root level of your repositories folder, you may specify the displayed
-# group name with this setting.  This value is only used for web presentation.
-#
-# SINCE 0.5.0
-web.repositoryRootGroupName = main
-
-# Display the repository swatch color next to the repository name link in the 
-# repositories list. 
-#
-# SINCE 0.8.0
-web.repositoryListSwatches = true
-
-# Defines the default commit message renderer.  This can be configured
-# per-repository.
-#
-# Valid values are: plain, markdown
-#
-# SINCE 1.4.0
-web.commitMessageRenderer = plain
-
-# Control if email addresses are shown in web ui
-#
-# SINCE 0.5.0
-web.showEmailAddresses = true
-
-# Shows a combobox in the page links header with commit, committer, and author
-# search selection.  Default search is commit.
-#
-# SINCE 0.5.0
-web.showSearchTypeSelection = false
-
-# Controls display of activity graphs on the dashboard, activity, and summary
-# pages.  Charting makes use of the external Google Charts API.
-#
-# SINCE 0.5.0 
-web.generateActivityGraph = true
-
-# Displays the commits branch graph in the summary page and commits/log page.
-#
-# SINCE 1.4.0
-web.showBranchGraph = true
-
-# The default number of days to show on the activity page.
-# Value must exceed 0 else default of 7 is used
-#
-# SINCE 0.8.0
-web.activityDuration = 7
-
-# Choices for days of activity to display.
-#
-# SPACE-DELIMITED
-# SINCE 1.3.0
-web.activityDurationChoices = 1 3 7 14 21 28
-
-# Maximum number of days of activity that may be displayed on the activity page.
-#
-# SINCE 1.3.2
-web.activityDurationMaximum = 30
-
-# The number of days of commits to cache in memory for the dashboard, activity,
-# and project pages.  A value of 0 will disable all caching and will parse commits
-# in each repository per-request.  If the value > 0 these pages will try to fulfill
-# requests using the commit cache.  If the request specifies a period which falls
-# outside the commit cache window, then the cache will be ignored and the request
-# will be fulfilled by brute-force parsing all relevant commits per-repository.
-#
-# Consider the values specified for *web.activityDurationChoices* when setting
-# the cache size AND consider adjusting the JVM -Xmx heap parameter appropriately.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-web.activityCacheDays = 14
-
-# Case-insensitive list of authors to exclude from metrics.  Useful for
-# eliminating bots.
-#
-# SPACE-DELIMITED
-# SINCE 1.3.0
-web.metricAuthorExclusions =
-
-# The number of commits to display on the summary page
-# Value must exceed 0 else default of 20 is used
-#
-# SINCE 0.5.0
-web.summaryCommitCount = 16
-
-# The number of tags/branches to display on the summary page.
-# -1 = all tags/branches
-# 0 = hide tags/branches
-# N = N tags/branches
-#
-# SINCE 0.5.0
-web.summaryRefsCount = 5
-
-# Show a README file, if available, on the summary page.
-#
-# SINCE 1.4.0
-web.summaryShowReadme = false
-
-# The number of items to show on a page before showing the first, prev, next
-# pagination links.  A default of 50 is used for any invalid value.
-#
-# SINCE 0.5.0
-web.itemsPerPage = 50
-
-# The number of reflog changes to display on the overview page
-# Value must exceed 0 else default of 5 is used
-#
-# SINCE 1.3.0
-web.overviewReflogCount = 5
-
-# The number of reflog changes to show on a reflog page before show the first,
-#  prev, next pagination links.  A default of 10 is used for any invalid value.
-#
-# SINCE 1.3.0
-web.reflogChangesPerPage = 10
-
-# Specify the names of documents in the root of your repository to be displayed
-# in tabs on your repository docs page.  If the name is not found in the root
-# then no tab is added.  The order specified is the order displayed.  Do not
-# specify a file extension as the aggregation of markup extensions + txt are used
-# in the search algorithm.
-#
-# SPACE-DELIMITED
-# SINCE 1.4.0
-web.documents = readme home index changelog contributing submitting_patches copying license notice authors
-
-# Registered file extensions to ignore during Lucene indexing
-#
-# SPACE-DELIMITED
-# SINCE 0.9.0
-web.luceneIgnoreExtensions = 7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt pptx png so swf tar xcf xls xlsx zip
-
-# Registered extensions for google-code-prettify
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.prettyPrintExtensions = aea agc basic c cbm cl clj cpp cs css dart el erl erlang frm fs go groovy h hpp hs htm html java js latex lisp ll llvm lsp lua ml moxie mumps n nemerle pascal php pl pm prefs properties proto py r R rb rd Rd rkt s S scala scm sh Splus sql ss tcl tex vb vbs vhd vhdl wiki xml xq xquery yaml yml ymlapollo
-
-# Registered extensions for markdown transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.5.0
-web.markdownExtensions = md mkd markdown MD MKD
-
-# Registered extensions for mediawiki transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.mediawikiExtensions = mw mediawiki
-
-# Registered extensions for twiki transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.twikiExtensions = twiki
-
-# Registered extensions for textile transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.textileExtensions = textile
-
-# Registered extensions for confluence transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.confluenceExtensions = confluence
-
-# Registered extensions for tracwiki transformation
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 1.4.0
-web.tracwikiExtensions = tracwiki
-
-# Image extensions
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.imageExtensions = bmp jpg jpeg gif png ico 
-
-# Registered extensions for binary blobs
-#
-# SPACE-DELIMITED
-# SINCE 0.5.0
-web.binaryExtensions = 7z arc arj bin dll doc docx exe gz jar lib lzh odg odf odt pdf ppt pptx so tar xls xlsx zip
-
-# Aggressive heap management will run the garbage collector on every generated
-# page.  This slows down page generation a little but improves heap consumption. 
-#
-# SINCE 0.5.0
-web.aggressiveHeapManagement = false
-
-# Run the webapp in debug mode
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-web.debugMode = false
-
-# Force a default locale for all users, ignoring the browser's settings.
-# An empty value allows Gitblit to use the translation preferred by the browser.
-#
-# Changing this value while the server is running will only affect new sessions.
-#
-# e.g. web.forceDefaultLocale = en
-#
-# SINCE 1.3.0
-web.forceDefaultLocale = 
-
-# Enable/disable global regex substitutions (i.e. shared across repositories)
-#
-# SINCE 0.5.0
-# DEPRECATED 1.4.0 (migrate to bugtraq instead)
-regex.global = true
-
-# Example global regex substitutions
-# Use !!! to separate the search pattern and the replace pattern
-# searchpattern!!!replacepattern
-# SINCE 0.5.0
-
-# regex.global.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://somehost/bug/$3">$3</a>
-# SINCE 0.5.0
-
-# Example Gerrit links
-# regex.global.changeid = \\b(Change-Id:\\s*)([A-Za-z0-9]*)\\b!!!Change-Id: <a href="http://somehost/r/#q,$2,n,z">$2</a>
-# regex.global.reviewedon = \\b(Reviewed-on:\\s*)([A-Za-z0-9:/\\.]*)\\b!!!Reviewed-on: <a href="$2">$2</a>
-
-# Example per-repository regex substitutions overrides global
-# SINCE 0.5.0
-# regex.myrepository.bug = \\b(Bug:)(\\s*[#]?|-){0,1}(\\d+)\\b!!!Bug: <a href="http://elsewhere/bug/$3">$3</a>
-
-#
-# Mail Settings
-# SINCE 0.6.0
-#
-# Mail settings are used to notify administrators of received federation proposals
-#
-
-# ip or hostname of smtp server
-#
-# SINCE 0.6.0
-mail.server =
-
-# port to use for smtp requests
-#
-# SINCE 0.6.0
-mail.port = 25
-
-# debug the mail executor
-#
-# SINCE 0.6.0
-mail.debug = false
-
-# use SMTPs flag
-mail.smtps = false
-
-# use STARTTLS flag
-#
-# SINCE 1.6.0
-mail.starttls = false
-
-# if your smtp server requires authentication, supply the credentials here
-#
-# SINCE 0.6.0
-mail.username =
-# SINCE 0.6.0
-mail.password =
-
-# from address for generated emails
-#
-# SINCE 0.6.0
-mail.fromAddress = 
-
-# List of email addresses for the Gitblit administrators
-#
-# SPACE-DELIMITED
-# SINCE 0.6.0
-mail.adminAddresses = 
-
-# List of email addresses for sending push email notifications.
-#
-# This key currently requires use of the sendemail.groovy hook script.
-# If you set sendemail.groovy in *groovy.postReceiveScripts* then email
-# notifications for all repositories (regardless of access restrictions!)
-# will be sent to these addresses.
-#
-# SPACE-DELIMITED
-# SINCE 0.8.0
-mail.mailingLists =
-
-#
-# Federation Settings
-# SINCE 0.6.0
-#
-# A Gitblit federation is a way to backup one Gitblit instance to another.
-#
-# *git.enableGitServlet* must be true to use this feature.
-
-# Your federation name is used for federation status acknowledgments.  If it is
-# unset, and you elect to send a status acknowledgment, your Gitblit instance
-# will be identified by its hostname, if available, else your internal ip address.
-# The source Gitblit instance will also append your external IP address to your
-# identification to differentiate multiple pulling systems behind a single proxy.
-#
-# SINCE 0.6.0
-federation.name =
-
-# Specify the passphrase of this Gitblit instance.
-#
-# An unspecified (empty) passphrase disables processing federation requests.
-#
-# This value can be anything you want: an integer, a sentence, an haiku, etc.
-# Keep the value simple, though, to avoid Java properties file encoding issues.
-#
-# Changing your passphrase will break any registrations you have established with other
-# Gitblit instances.
-#
-# CASE-SENSITIVE
-# SINCE 0.6.0
-# RESTART REQUIRED *(only to enable or disable federation)*
-federation.passphrase =
-
-# Control whether or not this Gitblit instance can receive federation proposals
-# from another Gitblit instance.  Registering a federated Gitblit is a manual
-# process.  Proposals help to simplify that process by allowing a remote Gitblit
-# instance to send your Gitblit instance the federation pull data.
-#
-# SINCE 0.6.0
-federation.allowProposals = false
-
-# The destination folder for cached federation proposals.
-# Use forward slashes even on Windows!!
-#
-# SINCE 0.6.0
-# BASEFOLDER
-federation.proposalsFolder = ${baseFolder}/proposals
-
-# The default pull frequency if frequency is unspecified on a registration
-#
-# SINCE 0.6.0
-federation.defaultFrequency = 60 mins
-
-# Federation Sets are named groups of repositories.  The Federation Sets are 
-# available for selection in the repository settings page.  You can assign a
-# repository to one or more sets and then distribute the token for the set.
-# This allows you to grant federation pull access to a subset of your available
-# repositories.  Tokens for federation sets only grant repository pull access.
-#
-# SPACE-DELIMITED
-# CASE-SENSITIVE
-# SINCE 0.6.0
-federation.sets = 
-
-# Federation pull registrations
-# Registrations are read once, at startup.
-#
-# RESTART REQUIRED
-#
-# frequency:
-#   The shortest frequency allowed is every 5 minutes
-#   Decimal frequency values are cast to integers
-#   Frequency values may be specified in mins, hours, or days
-#   Values that can not be parsed or are unspecified default to *federation.defaultFrequency*
-#
-# folder:
-#   if unspecified, the folder is *git.repositoriesFolder*
-#   if specified, the folder is relative to *git.repositoriesFolder*
-#
-# bare:
-#   if true, each repository will be created as a *bare* repository and will not
-#   have a working directory.
-#
-#   if false, each repository will be created as a normal repository suitable
-#   for local work.
-#
-# mirror:
-#   if true, each repository HEAD is reset to *origin/master* after each pull.
-#   The repository will be flagged *isFrozen* after the initial clone.
-#
-#   if false, each repository HEAD will point to the FETCH_HEAD of the initial
-#   clone from the origin until pushed to or otherwise manipulated.
-#
-# mergeAccounts:
-#   if true, remote accounts and their permissions are merged into your 
-#   users.properties file 
-#
-# notifyOnError:
-#   if true and the mail configuration is properly set, administrators will be
-#   notified by email of pull failures
-#
-# include and exclude:
-#   Space-delimited list of repositories to include or exclude from pull
-#   may be * wildcard to include or exclude all
-#   may use fuzzy match (e.g. org.eclipse.*)
-
-#
-# (Nearly) Perfect Mirror example
-#
-
-#federation.example1.url = https://go.gitblit.com
-#federation.example1.token = 6f3b8a24bf970f17289b234284c94f43eb42f0e4
-#federation.example1.frequency = 120 mins
-#federation.example1.folder =
-#federation.example1.bare = true 
-#federation.example1.mirror = true 
-#federation.example1.mergeAccounts = true
-
-#
-# Advanced Realm Settings
-#
-
-# Auto-creates user accounts based on the servlet container principal.  This
-# assumes that your Gitblit install is a protected resource and your container's
-# authentication process intercepts all Gitblit requests.
-#
-# SINCE 1.3.0
-realm.container.autoCreateAccounts = false
-
-# Allow or prohibit Windows guest account logins
-#
-# SINCE 1.3.0
-realm.windows.allowGuests = false
-
-# Allow user accounts belonging to the BUILTIN\Administrators group to be
-# Gitblit administrators.
-#
-# SINCE 1.4.0
-realm.windows.permitBuiltInAdministrators = true
-
-# The default domain for authentication.
-#
-# If specified, this domain will be used for authentication UNLESS the supplied
-# login name manually specifies a domain (.e.g. mydomain\james or james@mydomain)
-#
-# If unspecified, the username must be specified in UPN format (name@domain).
-#
-# if "." (dot) is specified, ONLY the local account database will be used.
-#
-# SINCE 1.3.0
-realm.windows.defaultDomain =
-
-# The PAM service name for authentication.
-# default: system-auth
-#
-# SINCE 1.3.1
-realm.pam.serviceName = system-auth
-
-# The Apache htpasswd file that contains the users and passwords.
-# default: ${baseFolder}/htpasswd
-#
-# RESTART REQUIRED
-# BASEFOLDER
-# SINCE 1.3.2
-realm.htpasswd.userfile = ${baseFolder}/htpasswd
-
-# Restrict the Salesforce user to members of this org.
-# default: 0 (i.e. do not check the Org ID)
-#
-# SINCE 1.3.0
-realm.salesforce.orgId = 0
-
-# URL of the LDAP server.
-# To use encrypted transport, use either ldaps:// URL for SSL or ldap+tls:// to
-# send StartTLS command.
-#
-# SINCE 1.0.0
-realm.ldap.server = ldap://localhost
-
-# Login username for LDAP searches.
-# If this value is unspecified, anonymous LDAP login will be used.
-# 
-# e.g. mydomain\\username
-#
-# SINCE 1.0.0
-realm.ldap.username = cn=Directory Manager
-
-# Login password for LDAP searches.
-#
-# SINCE 1.0.0
-realm.ldap.password = password
-
-# Bind pattern for Authentication.
-# Allow to directly authenticate an user without LDAP Searches.
-# 
-# e.g. CN=${username},OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
-#
-# SINCE 1.5.0
-realm.ldap.bindpattern = 
-
-
-# Delegate team membership control to LDAP.
-#
-# If true, team user memberships will be specified by LDAP groups.  This will
-# disable team selection in Edit User and user selection in Edit Team.
-#
-# If false, LDAP will only be used for authentication and Gitblit will maintain
-# team memberships with the *realm.ldap.backingUserService*.
-#
-# SINCE 1.0.0
-realm.ldap.maintainTeams = false
-
-# Root node for all LDAP users
-#
-# This is the root node from which subtree user searches will begin.
-# If blank, Gitblit will search ALL nodes.
-#
-# SINCE 1.0.0
-realm.ldap.accountBase = OU=Users,OU=UserControl,OU=MyOrganization,DC=MyDomain
-
-# Filter criteria for LDAP users
-#
-# Query pattern to use when searching for a user account. This may be any valid 
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# Variables may be injected via the ${variableName} syntax.
-# Recognized variables are:
-#    ${username} - The text entered as the user name
-#
-# SINCE 1.0.0
-realm.ldap.accountPattern = (&(objectClass=person)(sAMAccountName=${username}))
-
-# Root node for all LDAP groups to be used as Gitblit Teams
-#
-# This is the root node from which subtree team searches will begin.
-# If blank, Gitblit will search ALL nodes.  
-#
-# SINCE 1.0.0
-realm.ldap.groupBase = OU=Groups,OU=UserControl,OU=MyOrganization,DC=MyDomain
-
-# Filter criteria for LDAP groups
-#
-# Query pattern to use when searching for a team. This may be any valid 
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# Variables may be injected via the ${variableName} syntax.
-# Recognized variables are:
-#    ${username} - The text entered as the user name
-#    ${dn} - The Distinguished Name of the user logged in
-#
-# All attributes from the LDAP User record are available. For example, if a user
-# has an attribute "fullName" set to "John", "(fn=${fullName})" will be 
-# translated to "(fn=John)".
-#
-# SINCE 1.0.0
-realm.ldap.groupMemberPattern = (&(objectClass=group)(member=${dn}))
-
-# Filter criteria for empty LDAP groups
-#
-# Query pattern to use when searching for an empty team. This may be any valid 
-# LDAP query expression, including the standard (&) and (|) operators.
-#
-# default: (&(objectClass=group)(!(member=*)))
-# SINCE 1.4.0
-realm.ldap.groupEmptyMemberPattern = (&(objectClass=group)(!(member=*)))
-
-# LDAP users or groups that should be given administrator privileges.
-#
-# Teams are specified with a leading '@' character.  Groups with spaces in the
-# name can be entered as "@team name".  This setting only applies when using
-# LDAP to maintain team memberships.
-#
-# e.g. realm.ldap.admins = john @git_admins "@git admins"
-#
-# SPACE-DELIMITED
-# SINCE 1.0.0
-realm.ldap.admins = @Git_Admins
-
-# Attribute(s) on the USER record that indicate their display (or full) name.
-# Leave blank for no mapping available in LDAP.
-#
-# This may be a single attribute, or a string of multiple attributes.  Examples:
-#  displayName - Uses the attribute 'displayName' on the user record
-#  ${personalTitle}. ${givenName} ${surname} - Will concatenate the 3 
-#       attributes together, with a '.' after personalTitle
-#
-# SINCE 1.0.0
-realm.ldap.displayName = displayName
-
-# Attribute(s) on the USER record that indicate their email address.
-# Leave blank for no mapping available in LDAP.
-#
-# This may be a single attribute, or a string of multiple attributes.  Examples:
-#  email - Uses the attribute 'email' on the user record
-#  ${givenName}.${surname}@gitblit.com -Will concatenate the 2 attributes
-#       together with a '.' and '@' creating something like first.last@gitblit.com 
-#
-# SINCE 1.0.0
-realm.ldap.email = email
-
-# Attribute on the USER record that indicate their username to be used in gitblit
-# when synchronizing users from LDAP
-# if blank, Gitblit will use uid
-# For MS Active Directory this may be sAMAccountName
-#
-# SINCE 1.0.0
-realm.ldap.uid = uid
-
-# Defines whether to synchronize all LDAP users and teams into the user service
-#
-# Valid values: true, false
-# If left blank, false is assumed
-#
-# SINCE 1.4.0
-realm.ldap.synchronize = false
-
-# Defines the period to be used when synchronizing users and teams from ldap.
-#
-# Must be of the form '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS' 
-
-# default: 5 MINUTES
-#
-# RESTART REQUIRED
-# SINCE 1.4.0
-realm.ldap.syncPeriod = 5 MINUTES
-
-# Defines whether to delete non-existent LDAP users from the user service
-# during synchronization. depends on  realm.ldap.synchronize = true
-#
-# Valid values: true, false
-# If left blank, true is assumed
-#
-# SINCE 1.4.0
-realm.ldap.removeDeletedUsers = true
-
-# URL of the Redmine.
-#
-# SINCE 1.2.0
-realm.redmine.url = http://example.com/redmine
-
-#
-# Gitblit GO Server Settings
-# The following settings only affect the integrated GO variant.
-#
-
-# The temporary folder to decompress the embedded gitblit webapp. 
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-# BASEFOLDER
-server.tempFolder = ${baseFolder}/temp
-
-# Specify the maximum number of concurrent http/https Jetty worker
-# threads to allow.  This setting does not affect other threaded
-# daemons and components of Gitblit.
-#
-# SINCE 1.3.0
-# RESTART REQUIRED
-server.threadPoolSize = 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.
-#
-# SINCE 0.7.0
-# RESTART REQUIRED
-server.contextPath = /
-
-# Standard http port to serve.  <= 0 disables this connector.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 80 or 8080
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpPort = 0
-
-# Secure/SSL https port to serve. <= 0 disables this connector.
-# On Unix/Linux systems, ports < 1024 require root permissions.
-# Recommended value: 443 or 8443
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpsPort = 8443
-
-# Automatically redirect http requests to the secure https connector.
-#
-# This setting requires that you have configured server.httpPort and server.httpsPort.
-# Unless you are on a private LAN where you trust all client connections, it is
-# recommended to use https for all communications.
-#
-# SINCE 1.4.0
-# RESTART REQUIRED
-server.redirectToHttpsPort = false
-
-# Specify the interface for Jetty to bind the standard connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpBindInterface =
-
-# Specify the interface for Jetty to bind the secure connector.
-# You may specify an ip or an empty value to bind to all interfaces.
-# Specifying localhost will result in Gitblit ONLY listening to requests to
-# localhost.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.httpsBindInterface =
-
-# Alias of certificate to use for https/SSL serving.  If blank the first
-# certificate found in the keystore will be used. 
-#
-# SINCE 1.2.0
-# RESTART REQUIRED
-server.certificateAlias = localhost
-
-# Password for SSL keystore.
-# Keystore password and certificate password must match.
-# This is provided for convenience, its probably more secure to set this value
-# using the --storePassword command line parameter.
-#
-# If you are using the official JRE or JDK from Oracle you may not have the
-# JCE Unlimited Strength Jurisdiction Policy files bundled with your JVM.  Because
-# of this, your store/key password can not exceed 7 characters.  If you require
-# longer passwords you may need to install the JCE Unlimited Strength Jurisdiction
-# Policy files from Oracle.
-#
-# http://www.oracle.com/technetwork/java/javase/downloads/index.html
-#
-# Gitblit and the Gitblit Certificate Authority will both indicate if Unlimited
-# Strength encryption is available.
-#
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.storePassword = gitblit
-
-# If serving over https (recommended) you might consider requiring clients to
-# authenticate with ssl certificates.  If enabled, only https clients with the
-# a valid client certificate will be able to access Gitblit.
-#
-# If disabled, client certificate authentication is optional and will be tried
-# first before falling-back to form authentication or basic authentication.
-#
-# Requiring client certificates to access any of Gitblit may be too extreme,
-# consider this carefully.
-#
-# SINCE 1.2.0
-# RESTART REQUIRED
-server.requireClientCertificates = false
-
-# Port for shutdown monitor to listen on.
+# Define your overrides or custom settings below
 #
-# SINCE 0.5.0
-# RESTART REQUIRED
-server.shutdownPort = 8081
diff --git a/src/main/distrib/linux/install-service-fedora.sh b/src/main/distrib/linux/install-service-fedora.sh
index 281fa40..4fb43c6 100755
--- a/src/main/distrib/linux/install-service-fedora.sh
+++ b/src/main/distrib/linux/install-service-fedora.sh
@@ -1,5 +1,5 @@
 #!/bin/bash -x
-# This script installes gitblit on a system running under systemd.
+# This script installs Gitblit on a system running under systemd.
 # The script assumes the server is running as user giblit
 
 # First create a file with the default settings
@@ -8,7 +8,6 @@
 GITBLIT_BASE_FOLDER=/opt/gitblit/data
 GITBLIT_HTTP_PORT=0
 GITBLIT_HTTPS_PORT=8443
-GITBLIT_LOG=/var/log/gitblit.log
 EOF
 # Create a systemd service file
 cat > /tmp/gitblit.service << EOF
@@ -30,7 +29,5 @@
 EOF
 
 # Finally copy the files to the destination and register the systemd unit.
-sudo su -c "cp /tmp/gitblit.defaults /etc/sysconfig/gitblit && cp /tmp/gitblit.service /usr/lib/systemd/system/"
+sudo su -c "cp /tmp/gitblit.defaults /etc/sysconfig/gitblit && cp /tmp/gitblit.service /etc/systemd/system/"
 sudo su -c "systemctl daemon-reload && systemctl enable gitblit.service && systemctl start gitblit.service"
-# Prepare the logfile
-sudo su -c "touch /var/log/gitblit.log && chown gitblit:gitblit /var/log/gitblit.log"
diff --git a/src/main/java/.gitignore b/src/main/java/.gitignore
index b978906..92bb4af 100644
--- a/src/main/java/.gitignore
+++ b/src/main/java/.gitignore
@@ -1 +1,2 @@
 /clientapps.json
+/defaults.properties
diff --git a/src/main/java/WEB-INF/web.xml b/src/main/java/WEB-INF/web.xml
index 13f612e..db77215 100644
--- a/src/main/java/WEB-INF/web.xml
+++ b/src/main/java/WEB-INF/web.xml
@@ -1,361 +1,52 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<web-app version="2.4"
-	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
-
-	<!-- The base folder is used to specify the root location of your Gitblit data.
-	
-			${baseFolder}/gitblit.properties
-			${baseFolder}/users.conf
-			${baseFolder}/projects.conf
-			${baseFolder}/robots.txt
-			${baseFolder}/git
-			${baseFolder}/groovy
-			${baseFolder}/groovy/grape
-			${baseFolder}/proposals
-
-		By default, this location is WEB-INF/data.  It is recommended to set this
-		path to a location outside your webapps folder that is writable by your
-		servlet container.  Gitblit will copy the WEB-INF/data files to that
-		location for you when it restarts.  This approach makes upgrading simpler.
-		All you have to do is set this parameter for the new release and then
-		review the defaults for any new settings.  Settings are always versioned
-		with a SINCE x.y.z attribute and also noted in the release changelog.
-		-->
-	<env-entry>
-		<description>The base folder is used to specify the root location of your Gitblit data.</description>
-		<env-entry-name>baseFolder</env-entry-name>
-		<env-entry-type>java.lang.String</env-entry-type>
-		<env-entry-value>${contextFolder}/WEB-INF/data</env-entry-value>
-	</env-entry>
-	
-	<!-- Gitblit Displayname -->
-	<display-name>Gitblit - @gb.version@</display-name>
-
-	 
-<!-- Gitblit Context Listener --><!-- STRIP	 
-	<listener>
- 		<listener-class>com.gitblit.servlet.GitblitContext</listener-class>
- 	</listener>STRIP --> 	
-	
-	
-	<!-- Git Servlet
-		 <url-pattern> MUST match: 
-			* GitFilter
-			* com.gitblit.Constants.GIT_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>GitServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.GitServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>GitServlet</servlet-name>		
-		<url-pattern>/git/*</url-pattern>
-	</servlet-mapping>
-	<servlet-mapping>
-		<servlet-name>GitServlet</servlet-name>		
-		<url-pattern>/r/*</url-pattern>
-	</servlet-mapping>
-
-	
-	<!-- SparkleShare Invite Servlet
-		 <url-pattern> MUST match: 
-			* com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>SparkleShareInviteServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.SparkleShareInviteServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>SparkleShareInviteServlet</servlet-name>		
-		<url-pattern>/sparkleshare/*</url-pattern>
-	</servlet-mapping>
-
-	
-	<!-- Syndication Servlet
-		 <url-pattern> MUST match: 
-			* SyndicationFilter
-			* com.gitblit.Constants.SYNDICATION_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>SyndicationServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.SyndicationServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>SyndicationServlet</servlet-name>
-		<url-pattern>/feed/*</url-pattern>
-	</servlet-mapping>
-	
-	
-	<!-- Zip Servlet
-		 <url-pattern> MUST match: 
-			* ZipServlet
-			* com.gitblit.Constants.ZIP_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>ZipServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.DownloadZipServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>ZipServlet</servlet-name>
-		<url-pattern>/zip/*</url-pattern>
-	</servlet-mapping>
-	
-	
-	<!-- Federation Servlet
-		 <url-pattern> MUST match: 
-		 	* com.gitblit.Constants.FEDERATION_PATH		 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>FederationServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.FederationServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>FederationServlet</servlet-name>
-		<url-pattern>/federation/*</url-pattern>
-	</servlet-mapping>	
-	
-	
-	<!-- Rpc Servlet
-		 <url-pattern> MUST match: 
-		 	* com.gitblit.Constants.RPC_PATH		 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>RpcServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.RpcServlet</servlet-class>		
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>RpcServlet</servlet-name>
-		<url-pattern>/rpc/*</url-pattern>
-	</servlet-mapping>	
-
-
-	<!-- Raw Servlet
-		 <url-pattern> MUST match: 
-			* RawFilter
-			* com.gitblit.Constants.RAW_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>RawServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.RawServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>RawServlet</servlet-name>		
-		<url-pattern>/raw/*</url-pattern>
-	</servlet-mapping>	
-
-
-	<!-- Pages Servlet
-		 <url-pattern> MUST match: 
-			* PagesFilter
-			* com.gitblit.Constants.PAGES_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>PagesServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.PagesServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>PagesServlet</servlet-name>		
-		<url-pattern>/pages/*</url-pattern>
-	</servlet-mapping>	
-
-	
-	<!-- Logo Servlet
-		 <url-pattern> MUST match: 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>LogoServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.LogoServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>LogoServlet</servlet-name>		
-		<url-pattern>/logo.png</url-pattern>
-	</servlet-mapping>
-
-
-	<!-- PT Servlet
-		 <url-pattern> MUST match: 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>PtServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.PtServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>PtServlet</servlet-name>		
-		<url-pattern>/pt</url-pattern>
-	</servlet-mapping>
-
-
-	<!-- Branch Graph Servlet
-		 <url-pattern> MUST match: 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>BranchGraphServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.BranchGraphServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>BranchGraphServlet</servlet-name>		
-		<url-pattern>/graph/*</url-pattern>
-	</servlet-mapping>
-
-	<!-- Robots.txt Servlet
-		 <url-pattern> MUST match: 
-			* Wicket Filter ignorePaths parameter -->
-	<servlet>
-		<servlet-name>RobotsTxtServlet</servlet-name>
-		<servlet-class>com.gitblit.servlet.RobotsTxtServlet</servlet-class>
-	</servlet>
-	<servlet-mapping>
-		<servlet-name>RobotsTxtServlet</servlet-name>		
-		<url-pattern>/robots.txt</url-pattern>
-	</servlet-mapping>
-
-    <filter>
-		<filter-name>ProxyFilter</filter-name>
-		<filter-class>com.gitblit.servlet.ProxyFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>ProxyFilter</filter-name>
-		<url-pattern>/*</url-pattern>
-	</filter-mapping>
-	
-	<!-- Git Access Restriction Filter
-		 <url-pattern> MUST match: 
-			* GitServlet
-			* com.gitblit.Constants.GIT_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>GitFilter</filter-name>
-		<filter-class>com.gitblit.servlet.GitFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>GitFilter</filter-name>
-		<url-pattern>/git/*</url-pattern>
-	</filter-mapping>
-	<filter-mapping>
-		<filter-name>GitFilter</filter-name>
-		<url-pattern>/r/*</url-pattern>
-	</filter-mapping>
-	
-	
-	<!-- Syndication Restriction Filter
-		 <url-pattern> MUST match: 
-			* SyndicationServlet
-			* com.gitblit.Constants.SYNDICATION_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>SyndicationFilter</filter-name>
-		<filter-class>com.gitblit.servlet.SyndicationFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>SyndicationFilter</filter-name>
-		<url-pattern>/feed/*</url-pattern>
-	</filter-mapping>
-	
-	
-	<!-- Download Zip Restriction Filter
-		 <url-pattern> MUST match: 
-			* DownloadZipServlet
-			* com.gitblit.Constants.ZIP_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>ZipFilter</filter-name>
-		<filter-class>com.gitblit.servlet.DownloadZipFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>ZipFilter</filter-name>
-		<url-pattern>/zip/*</url-pattern>
-	</filter-mapping>
-
-		
-	<!-- Rpc Restriction Filter
-		 <url-pattern> MUST match: 
-			* RpcServlet
-			* com.gitblit.Constants.RPC_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>RpcFilter</filter-name>
-		<filter-class>com.gitblit.servlet.RpcFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>RpcFilter</filter-name>
-		<url-pattern>/rpc/*</url-pattern>
-	</filter-mapping>
-
-
-	<!-- Branch Restriction Filter
-		 <url-pattern> MUST match: 
-			* RawServlet
-			* com.gitblit.Constants.BRANCH_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>RawFilter</filter-name>
-		<filter-class>com.gitblit.servlet.RawFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>RawFilter</filter-name>
-		<url-pattern>/raw/*</url-pattern>
-	</filter-mapping>
-	
-
-	<!-- Pages Restriction Filter
-		 <url-pattern> MUST match: 
-			* PagesServlet
-			* com.gitblit.Constants.PAGES_PATH
-			* Wicket Filter ignorePaths parameter -->
-	<filter>
-		<filter-name>PagesFilter</filter-name>
-		<filter-class>com.gitblit.servlet.PagesFilter</filter-class>
-	</filter>
-	<filter-mapping>
-		<filter-name>PagesFilter</filter-name>
-		<url-pattern>/pages/*</url-pattern>
-	</filter-mapping>
-	
-	<filter>
-		<filter-name>EnforceAuthenticationFilter</filter-name>
-		<filter-class>com.gitblit.servlet.EnforceAuthenticationFilter</filter-class>
-	</filter>
-	<filter-mapping>
-        <filter-name>EnforceAuthenticationFilter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-
-
-	<!-- Wicket Filter -->
-    <filter>
-        <filter-name>wicketFilter</filter-name>
-        <filter-class>
-            com.gitblit.wicket.GitblitWicketFilter
-        </filter-class>        
-        <init-param>
-            <param-name>ignorePaths</param-name>
-            <!-- Paths should match 
-             	* SyndicationFilter <url-pattern>
-             	* SyndicationServlet <url-pattern>
-             	* com.gitblit.Constants.SYNDICATION_PATH
-             	* GitFilter <url-pattern>
-             	* GitServlet <url-pattern>
-             	* com.gitblit.Constants.GIT_PATH
-             	* SparkleshareInviteServlet <url-pattern>
-             	* com.gitblit.Constants.SPARKLESHARE_INVITE_PATH
-             	* Zipfilter <url-pattern>
-             	* ZipServlet <url-pattern>
-             	* com.gitblit.Constants.ZIP_PATH
-             	* FederationServlet <url-pattern>
-             	* RpcFilter <url-pattern>
-             	* RpcServlet <url-pattern>
-             	* RawFilter <url-pattern>
-             	* RawServlet <url-pattern>
-             	* PagesFilter <url-pattern>
-             	* PagesServlet <url-pattern>
-             	* com.gitblit.Constants.PAGES_PATH -->
-            <param-value>r/,git/,pt,feed/,zip/,federation/,rpc/,raw/,pages/,robots.txt,logo.png,graph/,sparkleshare/</param-value>
-        </init-param>
-    </filter>
-    <filter-mapping>
-        <filter-name>wicketFilter</filter-name>
-        <url-pattern>/*</url-pattern>
-    </filter-mapping>
-    
+<?xml version="1.0" encoding="UTF-8"?>
+<web-app version="2.4"
+	xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_3_0.xsd">
+
+	<!-- The base folder is used to specify the root location of your Gitblit data.
+	
+			${baseFolder}/gitblit.properties
+			${baseFolder}/users.conf
+			${baseFolder}/projects.conf
+			${baseFolder}/robots.txt
+			${baseFolder}/git
+			${baseFolder}/groovy
+			${baseFolder}/groovy/grape
+			${baseFolder}/proposals
+
+		By default, this location is WEB-INF/data.  It is recommended to set this
+		path to a location outside your webapps folder that is writable by your
+		servlet container.  Gitblit will copy the WEB-INF/data files to that
+		location for you when it restarts.  This approach makes upgrading simpler.
+		All you have to do is set this parameter for the new release and then
+		review the defaults for any new settings.  Settings are always versioned
+		with a SINCE x.y.z attribute and also noted in the release changelog.
+		-->
+	<env-entry>
+		<description>The base folder is used to specify the root location of your Gitblit data.</description>
+		<env-entry-name>baseFolder</env-entry-name>
+		<env-entry-type>java.lang.String</env-entry-type>
+		<env-entry-value>${contextFolder}/WEB-INF/data</env-entry-value>
+	</env-entry>
+	
+	<!-- Gitblit Displayname -->
+	<display-name>Gitblit - @gb.version@</display-name>
+
+	<listener>
+  		<listener-class>com.gitblit.servlet.GitblitContext</listener-class>
+	</listener>
+	
+	<filter>
+    	<filter-name>guiceFilter</filter-name>
+		<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
+	</filter>
+
+	<filter-mapping>
+		<filter-name>guiceFilter</filter-name>
+		<url-pattern>/*</url-pattern>
+	</filter-mapping>
+
+	<session-config>
+		<tracking-mode>COOKIE</tracking-mode>
+	</session-config>
 </web-app>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/AvatarGenerator.java b/src/main/java/com/gitblit/AvatarGenerator.java
new file mode 100644
index 0000000..0415f92
--- /dev/null
+++ b/src/main/java/com/gitblit/AvatarGenerator.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2015 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;
+
+public interface AvatarGenerator {
+
+	String getURL(String username, String emailaddress, boolean identicon, int width);
+
+}
diff --git a/src/main/java/com/gitblit/ConfigUserService.java b/src/main/java/com/gitblit/ConfigUserService.java
index d7d6c14..200ec8a 100644
--- a/src/main/java/com/gitblit/ConfigUserService.java
+++ b/src/main/java/com/gitblit/ConfigUserService.java
@@ -37,6 +37,7 @@
 
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Constants.Transport;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.TeamModel;
@@ -734,22 +735,22 @@
 			// user roles
 			List<String> roles = new ArrayList<String>();
 			if (model.canAdmin) {
-				roles.add(Constants.ADMIN_ROLE);
+				roles.add(Role.ADMIN.getRole());
 			}
 			if (model.canFork) {
-				roles.add(Constants.FORK_ROLE);
+				roles.add(Role.FORK.getRole());
 			}
 			if (model.canCreate) {
-				roles.add(Constants.CREATE_ROLE);
+				roles.add(Role.CREATE.getRole());
 			}
 			if (model.excludeFromFederation) {
-				roles.add(Constants.NOT_FEDERATED_ROLE);
+				roles.add(Role.NOT_FEDERATED.getRole());
 			}
 			if (roles.size() == 0) {
 				// we do this to ensure that user record with no password
 				// is written.  otherwise, StoredConfig optimizes that account
 				// away. :(
-				roles.add(Constants.NO_ROLE);
+				roles.add(Role.NONE.getRole());
 			}
 			config.setStringList(USER, model.username, ROLE, roles);
 
@@ -778,18 +779,18 @@
 			// team roles
 			List<String> roles = new ArrayList<String>();
 			if (model.canAdmin) {
-				roles.add(Constants.ADMIN_ROLE);
+				roles.add(Role.ADMIN.getRole());
 			}
 			if (model.canFork) {
-				roles.add(Constants.FORK_ROLE);
+				roles.add(Role.FORK.getRole());
 			}
 			if (model.canCreate) {
-				roles.add(Constants.CREATE_ROLE);
+				roles.add(Role.CREATE.getRole());
 			}
 			if (roles.size() == 0) {
 				// we do this to ensure that team record is written.
 				// Otherwise, StoredConfig might optimizes that record away.
-				roles.add(Constants.NO_ROLE);
+				roles.add(Role.NONE.getRole());
 			}
 			config.setStringList(TEAM, model.name, ROLE, roles);
 			if (model.accountType != null) {
@@ -911,10 +912,10 @@
 					// user roles
 					Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
 							USER, username, ROLE)));
-					user.canAdmin = roles.contains(Constants.ADMIN_ROLE);
-					user.canFork = roles.contains(Constants.FORK_ROLE);
-					user.canCreate = roles.contains(Constants.CREATE_ROLE);
-					user.excludeFromFederation = roles.contains(Constants.NOT_FEDERATED_ROLE);
+					user.canAdmin = roles.contains(Role.ADMIN.getRole());
+					user.canFork = roles.contains(Role.FORK.getRole());
+					user.canCreate = roles.contains(Role.CREATE.getRole());
+					user.excludeFromFederation = roles.contains(Role.NOT_FEDERATED.getRole());
 
 					// repository memberships
 					if (!user.canAdmin) {
@@ -947,9 +948,9 @@
 					TeamModel team = new TeamModel(teamname);
 					Set<String> roles = new HashSet<String>(Arrays.asList(config.getStringList(
 							TEAM, teamname, ROLE)));
-					team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
-					team.canFork = roles.contains(Constants.FORK_ROLE);
-					team.canCreate = roles.contains(Constants.CREATE_ROLE);
+					team.canAdmin = roles.contains(Role.ADMIN.getRole());
+					team.canFork = roles.contains(Role.FORK.getRole());
+					team.canCreate = roles.contains(Role.CREATE.getRole());
 					team.accountType = AccountType.fromString(config.getString(TEAM, teamname, ACCOUNTTYPE));
 
 					if (!team.canAdmin) {
diff --git a/src/main/java/com/gitblit/Constants.java b/src/main/java/com/gitblit/Constants.java
index fa8af25..4aa8c0c 100644
--- a/src/main/java/com/gitblit/Constants.java
+++ b/src/main/java/com/gitblit/Constants.java
@@ -36,14 +36,19 @@
 
 	public static final String FULL_NAME = "Gitblit - a pure Java Git solution";
 
+	@Deprecated
 	public static final String ADMIN_ROLE = "#admin";
 
+	@Deprecated
 	public static final String FORK_ROLE = "#fork";
 
+	@Deprecated
 	public static final String CREATE_ROLE = "#create";
 
+	@Deprecated
 	public static final String NOT_FEDERATED_ROLE = "#notfederated";
 
+	@Deprecated
 	public static final String NO_ROLE = "#none";
 
 	public static final String EXTERNAL_ACCOUNT = "#externalAccount";
@@ -55,6 +60,8 @@
 	public static final String R_PATH = "/r/";
 
 	public static final String GIT_PATH = "/git/";
+	
+	public static final String REGEX_SHA256 = "[a-fA-F0-9]{64}";
 
 	public static final String ZIP_PATH = "/zip/";
 
@@ -69,6 +76,8 @@
 	public static final String SPARKLESHARE_INVITE_PATH = "/sparkleshare/";
 
 	public static final String RAW_PATH = "/raw/";
+
+	public static final String PT_PATH = "/pt";
 
 	public static final String BRANCH_GRAPH_PATH = "/graph/";
 
@@ -130,7 +139,11 @@
 
 	public static final String DEVELOP = "develop";
 
-	public static final String AUTHENTICATION_TYPE = "authentication-type";
+	public static final String ATTRIB_AUTHTYPE = NAME + ":authentication-type";
+
+	public static final String ATTRIB_AUTHUSER = NAME + ":authenticated-user";
+	
+	public static final String R_LFS = "info/lfs/";
 
 	public static String getVersion() {
 		String v = Constants.class.getPackage().getImplementationVersion();
@@ -146,6 +159,17 @@
 
 	public static String getBuildDate() {
 		return getManifestValue("build-date", "PENDING");
+	}
+
+	public static String getASCIIArt() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("  _____  _  _    _      _  _  _").append('\n');
+		sb.append(" |  __ \\(_)| |  | |    | |(_)| |").append('\n');
+		sb.append(" | |  \\/ _ | |_ | |__  | | _ | |_").append('\n');
+		sb.append(" | | __ | || __|| '_ \\ | || || __|").append("  ").append("http://gitblit.com").append('\n');
+		sb.append(" | |_\\ \\| || |_ | |_) || || || |_").append("   ").append("@gitblit").append('\n');
+		sb.append("  \\____/|_| \\__||_.__/ |_||_| \\__|").append("  ").append(Constants.getVersion()).append('\n');
+		return sb.toString();
 	}
 
 	private static String getManifestValue(String attrib, String defaultValue) {
@@ -167,6 +191,19 @@
 		return defaultValue;
 	}
 
+	public static enum Role {
+		NONE, ADMIN, CREATE, FORK, NOT_FEDERATED;
+
+		public String getRole() {
+			return "#" + name().replace("_", "").toLowerCase();
+		}
+
+		@Override
+		public String toString() {
+			return getRole();
+		}
+	}
+
 	/**
 	 * Enumeration representing the four access restriction levels.
 	 */
diff --git a/src/main/java/com/gitblit/DaggerModule.java b/src/main/java/com/gitblit/DaggerModule.java
deleted file mode 100644
index dd7e1b2..0000000
--- a/src/main/java/com/gitblit/DaggerModule.java
+++ /dev/null
@@ -1,222 +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;
-
-import javax.inject.Singleton;
-
-import com.gitblit.manager.AuthenticationManager;
-import com.gitblit.manager.FederationManager;
-import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.manager.IFederationManager;
-import com.gitblit.manager.IGitblit;
-import com.gitblit.manager.INotificationManager;
-import com.gitblit.manager.IPluginManager;
-import com.gitblit.manager.IProjectManager;
-import com.gitblit.manager.IRepositoryManager;
-import com.gitblit.manager.IRuntimeManager;
-import com.gitblit.manager.IUserManager;
-import com.gitblit.manager.NotificationManager;
-import com.gitblit.manager.PluginManager;
-import com.gitblit.manager.ProjectManager;
-import com.gitblit.manager.RepositoryManager;
-import com.gitblit.manager.RuntimeManager;
-import com.gitblit.manager.UserManager;
-import com.gitblit.transport.ssh.FileKeyManager;
-import com.gitblit.transport.ssh.IPublicKeyManager;
-import com.gitblit.transport.ssh.MemoryKeyManager;
-import com.gitblit.transport.ssh.NullKeyManager;
-import com.gitblit.utils.JSoupXssFilter;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.XssFilter;
-import com.gitblit.wicket.GitBlitWebApp;
-
-import dagger.Module;
-import dagger.Provides;
-
-/**
- * DaggerModule references all injectable objects.
- *
- * @author James Moger
- *
- */
-@Module(
-	library = true,
-	injects = {
-			IStoredSettings.class,
-			XssFilter.class,
-
-			// core managers
-			IRuntimeManager.class,
-			IPluginManager.class,
-			INotificationManager.class,
-			IUserManager.class,
-			IAuthenticationManager.class,
-			IPublicKeyManager.class,
-			IRepositoryManager.class,
-			IProjectManager.class,
-			IFederationManager.class,
-
-			// the monolithic manager
-			IGitblit.class,
-
-			// the Gitblit Wicket app
-			GitBlitWebApp.class
-		}
-)
-public class DaggerModule {
-
-	@Provides @Singleton IStoredSettings provideSettings() {
-		return new FileSettings();
-	}
-
-	@Provides @Singleton XssFilter provideXssFilter() {
-		return new JSoupXssFilter();
-	}
-
-	@Provides @Singleton IRuntimeManager provideRuntimeManager(IStoredSettings settings, XssFilter xssFilter) {
-		return new RuntimeManager(settings, xssFilter);
-	}
-
-	@Provides @Singleton IPluginManager providePluginManager(IRuntimeManager runtimeManager) {
-		return new PluginManager(runtimeManager);
-	}
-
-	@Provides @Singleton INotificationManager provideNotificationManager(IStoredSettings settings) {
-		return new NotificationManager(settings);
-	}
-
-	@Provides @Singleton IUserManager provideUserManager(
-			IRuntimeManager runtimeManager,
-			IPluginManager pluginManager) {
-
-		return new UserManager(runtimeManager, pluginManager);
-	}
-
-	@Provides @Singleton IAuthenticationManager provideAuthenticationManager(
-			IRuntimeManager runtimeManager,
-			IUserManager userManager) {
-
-		return new AuthenticationManager(
-				runtimeManager,
-				userManager);
-	}
-
-	@Provides @Singleton IPublicKeyManager providePublicKeyManager(
-			IStoredSettings settings,
-			IRuntimeManager runtimeManager) {
-
-		String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName());
-		if (StringUtils.isEmpty(clazz)) {
-			clazz = FileKeyManager.class.getName();
-		}
-		if (FileKeyManager.class.getName().equals(clazz)) {
-			return new FileKeyManager(runtimeManager);
-		} else if (NullKeyManager.class.getName().equals(clazz)) {
-			return new NullKeyManager();
-		} else if (MemoryKeyManager.class.getName().equals(clazz)) {
-			return new MemoryKeyManager();
-		} else {
-			try {
-				Class<?> mgrClass = Class.forName(clazz);
-				return (IPublicKeyManager) mgrClass.newInstance();
-			} catch (Exception e) {
-
-			}
-			return null;
-		}
-	}
-
-	@Provides @Singleton IRepositoryManager provideRepositoryManager(
-			IRuntimeManager runtimeManager,
-			IPluginManager pluginManager,
-			IUserManager userManager) {
-
-		return new RepositoryManager(
-				runtimeManager,
-				pluginManager,
-				userManager);
-	}
-
-	@Provides @Singleton IProjectManager provideProjectManager(
-			IRuntimeManager runtimeManager,
-			IUserManager userManager,
-			IRepositoryManager repositoryManager) {
-
-		return new ProjectManager(
-				runtimeManager,
-				userManager,
-				repositoryManager);
-	}
-
-	@Provides @Singleton IFederationManager provideFederationManager(
-			IRuntimeManager runtimeManager,
-			INotificationManager notificationManager,
-			IRepositoryManager repositoryManager) {
-
-		return new FederationManager(
-				runtimeManager,
-				notificationManager,
-				repositoryManager);
-	}
-
-	@Provides @Singleton IGitblit provideGitblit(
-			IRuntimeManager runtimeManager,
-			IPluginManager pluginManager,
-			INotificationManager notificationManager,
-			IUserManager userManager,
-			IAuthenticationManager authenticationManager,
-			IPublicKeyManager publicKeyManager,
-			IRepositoryManager repositoryManager,
-			IProjectManager projectManager,
-			IFederationManager federationManager) {
-
-		return new GitBlit(
-				runtimeManager,
-				pluginManager,
-				notificationManager,
-				userManager,
-				authenticationManager,
-				publicKeyManager,
-				repositoryManager,
-				projectManager,
-				federationManager);
-	}
-
-	@Provides @Singleton GitBlitWebApp provideWebApplication(
-			IRuntimeManager runtimeManager,
-			IPluginManager pluginManager,
-			INotificationManager notificationManager,
-			IUserManager userManager,
-			IAuthenticationManager authenticationManager,
-			IPublicKeyManager publicKeyManager,
-			IRepositoryManager repositoryManager,
-			IProjectManager projectManager,
-			IFederationManager federationManager,
-			IGitblit gitblit) {
-
-		return new GitBlitWebApp(
-				runtimeManager,
-				pluginManager,
-				notificationManager,
-				userManager,
-				authenticationManager,
-				publicKeyManager,
-				repositoryManager,
-				projectManager,
-				federationManager,
-				gitblit);
-	}
-}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/FederationClient.java b/src/main/java/com/gitblit/FederationClient.java
index 079355e..64ff017 100644
--- a/src/main/java/com/gitblit/FederationClient.java
+++ b/src/main/java/com/gitblit/FederationClient.java
@@ -1,192 +1,192 @@
-/*
- * Copyright 2011 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;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-import org.kohsuke.args4j.CmdLineException;
-import org.kohsuke.args4j.CmdLineParser;
-import org.kohsuke.args4j.Option;
-
-import com.gitblit.manager.FederationManager;
-import com.gitblit.manager.GitblitManager;
-import com.gitblit.manager.IGitblit;
-import com.gitblit.manager.INotificationManager;
-import com.gitblit.manager.RepositoryManager;
-import com.gitblit.manager.RuntimeManager;
-import com.gitblit.manager.UserManager;
-import com.gitblit.models.FederationModel;
-import com.gitblit.models.Mailing;
-import com.gitblit.service.FederationPullService;
-import com.gitblit.utils.FederationUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.XssFilter;
-import com.gitblit.utils.XssFilter.AllowXssFilter;
-
-/**
- * Command-line client to pull federated Gitblit repositories.
- *
- * @author James Moger
- *
- */
-public class FederationClient {
-
-	public static void main(String[] args) {
-		Params params = new Params();
-		CmdLineParser parser = new CmdLineParser(params);
-		try {
-			parser.parseArgument(args);
-		} catch (CmdLineException t) {
-			usage(parser, t);
-		}
-
-		System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
-
-		// command-line specified base folder
-		File baseFolder = new File(System.getProperty("user.dir"));
-		if (!StringUtils.isEmpty(params.baseFolder)) {
-			baseFolder = new File(params.baseFolder);
-		}
-
-		File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
-		FileSettings settings = new FileSettings(regFile.getAbsolutePath());
-		List<FederationModel> registrations = new ArrayList<FederationModel>();
-		if (StringUtils.isEmpty(params.url)) {
-			registrations.addAll(FederationUtils.getFederationRegistrations(settings));
-		} else {
-			if (StringUtils.isEmpty(params.token)) {
-				System.out.println("Must specify --token parameter!");
-				System.exit(0);
-			}
-			FederationModel model = new FederationModel("Gitblit");
-			model.url = params.url;
-			model.token = params.token;
-			model.mirror = params.mirror;
-			model.bare = params.bare;
-			model.folder = "";
-			registrations.add(model);
-		}
-		if (registrations.size() == 0) {
-			System.out.println("No Federation Registrations!  Nothing to do.");
-			System.exit(0);
-		}
-
-		// command-line specified repositories folder
-		if (!StringUtils.isEmpty(params.repositoriesFolder)) {
-			settings.overrideSetting(Keys.git.repositoriesFolder, new File(
-					params.repositoriesFolder).getAbsolutePath());
-		}
-
-		// configure the Gitblit singleton for minimal, non-server operation
-		XssFilter xssFilter = new AllowXssFilter();
-		RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start();
-		NoopNotificationManager notifications = new NoopNotificationManager().start();
-		UserManager users = new UserManager(runtime, null).start();
-		RepositoryManager repositories = new RepositoryManager(runtime, null, users).start();
-		FederationManager federation = new FederationManager(runtime, notifications, repositories).start();
-		IGitblit gitblit = new GitblitManager(runtime, null, notifications, users, null, null, repositories, null, federation);
-
-		FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) {
-			@Override
-			public void reschedule(FederationModel registration) {
-				// NOOP
-			}
-		};
-		puller.run();
-
-		System.out.println("Finished.");
-		System.exit(0);
-	}
-
-	private static void usage(CmdLineParser parser, CmdLineException t) {
-		System.out.println(Constants.getGitBlitVersion());
-		System.out.println();
-		if (t != null) {
-			System.out.println(t.getMessage());
-			System.out.println();
-		}
-
-		if (parser != null) {
-			parser.printUsage(System.out);
-		}
-		System.exit(0);
-	}
-
-	/**
-	 * Parameters class for FederationClient.
-	 */
-	private static class Params {
-
-		@Option(name = "--registrations", usage = "Gitblit Federation Registrations File", metaVar = "FILE")
-		public String registrationsFile = "${baseFolder}/federation.properties";
-
-		@Option(name = "--url", usage = "URL of Gitblit instance to mirror from", metaVar = "URL")
-		public String url;
-
-		@Option(name = "--mirror", usage = "Mirror repositories")
-		public boolean mirror;
-
-		@Option(name = "--bare", usage = "Create bare repositories")
-		public boolean bare;
-
-		@Option(name = "--token", usage = "Federation Token", metaVar = "TOKEN")
-		public String token;
-
-		@Option(name = "--baseFolder", usage = "Base folder for received data", metaVar = "PATH")
-		public String baseFolder;
-
-		@Option(name = "--repositoriesFolder", usage = "Destination folder for cloned repositories", metaVar = "PATH")
-		public String repositoriesFolder;
-
-	}
-
-	private static class NoopNotificationManager implements INotificationManager {
-
-		@Override
-		public NoopNotificationManager start() {
-			return this;
-		}
-
-		@Override
-		public NoopNotificationManager stop() {
-			return this;
-		}
-
-		@Override
-		public boolean isSendingMail() {
-			return false;
-		}
-
-		@Override
-		public void sendMailToAdministrators(String subject, String message) {
-		}
-
-		@Override
-		public void sendMail(String subject, String message, Collection<String> toAddresses) {
-		}
-
-		@Override
-		public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
-		}
-
-		@Override
-		public void send(Mailing mailing) {
-		}
-	}
-}
+/*
+ * Copyright 2011 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;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.Option;
+
+import com.gitblit.manager.FederationManager;
+import com.gitblit.manager.GitblitManager;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.Mailing;
+import com.gitblit.service.FederationPullService;
+import com.gitblit.utils.FederationUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+
+/**
+ * Command-line client to pull federated Gitblit repositories.
+ *
+ * @author James Moger
+ *
+ */
+public class FederationClient {
+
+	public static void main(String[] args) {
+		Params params = new Params();
+		CmdLineParser parser = new CmdLineParser(params);
+		try {
+			parser.parseArgument(args);
+		} catch (CmdLineException t) {
+			usage(parser, t);
+		}
+
+		System.out.println("Gitblit Federation Client v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
+
+		// command-line specified base folder
+		File baseFolder = new File(System.getProperty("user.dir"));
+		if (!StringUtils.isEmpty(params.baseFolder)) {
+			baseFolder = new File(params.baseFolder);
+		}
+
+		File regFile = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.registrationsFile);
+		FileSettings settings = new FileSettings(regFile.getAbsolutePath());
+		List<FederationModel> registrations = new ArrayList<FederationModel>();
+		if (StringUtils.isEmpty(params.url)) {
+			registrations.addAll(FederationUtils.getFederationRegistrations(settings));
+		} else {
+			if (StringUtils.isEmpty(params.token)) {
+				System.out.println("Must specify --token parameter!");
+				System.exit(0);
+			}
+			FederationModel model = new FederationModel("Gitblit");
+			model.url = params.url;
+			model.token = params.token;
+			model.mirror = params.mirror;
+			model.bare = params.bare;
+			model.folder = "";
+			registrations.add(model);
+		}
+		if (registrations.size() == 0) {
+			System.out.println("No Federation Registrations!  Nothing to do.");
+			System.exit(0);
+		}
+
+		// command-line specified repositories folder
+		if (!StringUtils.isEmpty(params.repositoriesFolder)) {
+			settings.overrideSetting(Keys.git.repositoriesFolder, new File(
+					params.repositoriesFolder).getAbsolutePath());
+		}
+
+		// configure the Gitblit singleton for minimal, non-server operation
+		XssFilter xssFilter = new AllowXssFilter();
+		RuntimeManager runtime = new RuntimeManager(settings, xssFilter, baseFolder).start();
+		NoopNotificationManager notifications = new NoopNotificationManager().start();
+		UserManager users = new UserManager(runtime, null).start();
+		RepositoryManager repositories = new RepositoryManager(runtime, null, users).start();
+		FederationManager federation = new FederationManager(runtime, notifications, repositories).start();
+		IGitblit gitblit = new GitblitManager(null, null, runtime, null, notifications, users, null, repositories, null, federation, null);
+
+		FederationPullService puller = new FederationPullService(gitblit, federation.getFederationRegistrations()) {
+			@Override
+			public void reschedule(FederationModel registration) {
+				// NOOP
+			}
+		};
+		puller.run();
+
+		System.out.println("Finished.");
+		System.exit(0);
+	}
+
+	private static void usage(CmdLineParser parser, CmdLineException t) {
+		System.out.println(Constants.getGitBlitVersion());
+		System.out.println();
+		if (t != null) {
+			System.out.println(t.getMessage());
+			System.out.println();
+		}
+
+		if (parser != null) {
+			parser.printUsage(System.out);
+		}
+		System.exit(0);
+	}
+
+	/**
+	 * Parameters class for FederationClient.
+	 */
+	private static class Params {
+
+		@Option(name = "--registrations", usage = "Gitblit Federation Registrations File", metaVar = "FILE")
+		public String registrationsFile = "${baseFolder}/federation.properties";
+
+		@Option(name = "--url", usage = "URL of Gitblit instance to mirror from", metaVar = "URL")
+		public String url;
+
+		@Option(name = "--mirror", usage = "Mirror repositories")
+		public boolean mirror;
+
+		@Option(name = "--bare", usage = "Create bare repositories")
+		public boolean bare;
+
+		@Option(name = "--token", usage = "Federation Token", metaVar = "TOKEN")
+		public String token;
+
+		@Option(name = "--baseFolder", usage = "Base folder for received data", metaVar = "PATH")
+		public String baseFolder;
+
+		@Option(name = "--repositoriesFolder", usage = "Destination folder for cloned repositories", metaVar = "PATH")
+		public String repositoriesFolder;
+
+	}
+
+	private static class NoopNotificationManager implements INotificationManager {
+
+		@Override
+		public NoopNotificationManager start() {
+			return this;
+		}
+
+		@Override
+		public NoopNotificationManager stop() {
+			return this;
+		}
+
+		@Override
+		public boolean isSendingMail() {
+			return false;
+		}
+
+		@Override
+		public void sendMailToAdministrators(String subject, String message) {
+		}
+
+		@Override
+		public void sendMail(String subject, String message, Collection<String> toAddresses) {
+		}
+
+		@Override
+		public void sendHtmlMail(String subject, String message, Collection<String> toAddresses) {
+		}
+
+		@Override
+		public void send(Mailing mailing) {
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/FileSettings.java b/src/main/java/com/gitblit/FileSettings.java
index 21a2043..3caf966 100644
--- a/src/main/java/com/gitblit/FileSettings.java
+++ b/src/main/java/com/gitblit/FileSettings.java
@@ -18,10 +18,13 @@
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
 import com.gitblit.utils.FileUtils;
+import com.gitblit.utils.StringUtils;
 
 /**
  * Dynamically loads and reloads a properties file by keeping track of the last
@@ -77,9 +80,13 @@
 		if (propertiesFile != null && propertiesFile.exists() && (forceReload || (propertiesFile.lastModified() > lastModified))) {
 			FileInputStream is = null;
 			try {
+				logger.debug("loading {}", propertiesFile);
 				Properties props = new Properties();
 				is = new FileInputStream(propertiesFile);
 				props.load(is);
+
+				// ticket-110
+				props = readIncludes(props);
 
 				// load properties after we have successfully read file
 				properties.clear();
@@ -103,12 +110,68 @@
 		return properties;
 	}
 
+	/**
+	 * Recursively read "include" properties files.
+	 *
+	 * @param properties
+	 * @return
+	 * @throws IOException
+	 */
+	private Properties readIncludes(Properties properties) throws IOException {
+
+		Properties baseProperties = new Properties();
+
+		String include = (String) properties.remove("include");
+		if (!StringUtils.isEmpty(include)) {
+
+			// allow for multiples
+			List<String> names = StringUtils.getStringsFromValue(include, ",");
+			for (String name : names) {
+
+				if (StringUtils.isEmpty(name)) {
+					continue;
+				}
+
+				// try co-located
+				File file = new File(propertiesFile.getParentFile(), name.trim());
+				if (!file.exists()) {
+					// try absolute path
+					file = new File(name.trim());
+				}
+
+				if (!file.exists()) {
+					logger.warn("failed to locate {}", file);
+					continue;
+				}
+
+				// load properties
+				logger.debug("loading {}", file);
+				try (FileInputStream iis = new FileInputStream(file)) {
+					baseProperties.load(iis);
+				}
+
+				// read nested includes
+				baseProperties = readIncludes(baseProperties);
+
+			}
+
+		}
+
+		// includes are "default" properties, they must be set first and the
+		// props which specified the "includes" must override
+		Properties merged = new Properties();
+		merged.putAll(baseProperties);
+		merged.putAll(properties);
+
+		return merged;
+	}
+
 	@Override
 	public boolean saveSettings() {
 		String content = FileUtils.readContent(propertiesFile, "\n");
 		for (String key : removals) {
 			String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)"
-				    + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+					+ "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
 			content = content.replaceAll(regex, "");
 		}
 		removals.clear();
@@ -128,7 +191,7 @@
 		String content = FileUtils.readContent(propertiesFile, "\n");
 		for (Map.Entry<String, String> setting:settings.entrySet()) {
 			String regex = "(?m)^(" + regExEscape(setting.getKey()) + "\\s*+=\\s*+)"
-				    + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
+					+ "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
 			String oldContent = content;
 			content = content.replaceAll(regex, setting.getKey() + " = " + setting.getValue());
 			if (content.equals(oldContent)) {
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index f9d9be9..4e25d5c 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -15,471 +15,58 @@
  */
 package com.gitblit;
 
-import java.text.MessageFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.inject.Singleton;
-import javax.servlet.http.HttpServletRequest;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.Transport;
 import com.gitblit.manager.GitblitManager;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
-import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.IFilestoreManager;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.manager.IUserManager;
-import com.gitblit.manager.ServicesManager;
-import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
-import com.gitblit.models.UserModel;
-import com.gitblit.tickets.BranchTicketService;
-import com.gitblit.tickets.FileTicketService;
 import com.gitblit.tickets.ITicketService;
-import com.gitblit.tickets.NullTicketService;
-import com.gitblit.tickets.RedisTicketService;
 import com.gitblit.transport.ssh.IPublicKeyManager;
-import com.gitblit.utils.StringUtils;
-
-import dagger.Module;
-import dagger.ObjectGraph;
-import dagger.Provides;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 /**
- * GitBlit is the aggregate manager for the Gitblit webapp.  It provides all
- * management functions and also manages some long-running services.
+ * GitBlit is the aggregate manager for the Gitblit webapp.  The parent class provides all
+ * functionality.  This class exists to not break existing Groovy push hooks.
  *
  * @author James Moger
  *
  */
+@Singleton
+@Deprecated
 public class GitBlit extends GitblitManager {
 
-	private final ObjectGraph injector;
-
-	private final ServicesManager servicesManager;
-
-	private ITicketService ticketService;
-
+	@Inject
 	public GitBlit(
+			Provider<IPublicKeyManager> publicKeyManagerProvider,
+			Provider<ITicketService> ticketServiceProvider,
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
 			INotificationManager notificationManager,
 			IUserManager userManager,
 			IAuthenticationManager authenticationManager,
-			IPublicKeyManager publicKeyManager,
 			IRepositoryManager repositoryManager,
 			IProjectManager projectManager,
-			IFederationManager federationManager) {
+			IFederationManager federationManager,
+			IFilestoreManager filestoreManager) {
 
-		super(runtimeManager,
+		super(
+				publicKeyManagerProvider,
+				ticketServiceProvider,
+				runtimeManager,
 				pluginManager,
 				notificationManager,
 				userManager,
 				authenticationManager,
-				publicKeyManager,
 				repositoryManager,
 				projectManager,
-				federationManager);
-
-		this.injector = ObjectGraph.create(getModules());
-
-		this.servicesManager = new ServicesManager(this);
-	}
-
-	@Override
-	public GitBlit start() {
-		super.start();
-		logger.info("Starting services manager...");
-		servicesManager.start();
-		configureTicketService();
-		return this;
-	}
-
-	@Override
-	public GitBlit stop() {
-		super.stop();
-		servicesManager.stop();
-		ticketService.stop();
-		return this;
-	}
-
-	@Override
-	public boolean isServingRepositories() {
-		return servicesManager.isServingRepositories();
-	}
-
-	@Override
-	public boolean isServingHTTP() {
-		return servicesManager.isServingHTTP();
-	}
-
-	@Override
-	public boolean isServingGIT() {
-		return servicesManager.isServingGIT();
-	}
-
-	@Override
-	public boolean isServingSSH() {
-		return servicesManager.isServingSSH();
-	}
-
-	protected Object [] getModules() {
-		return new Object [] { new GitBlitModule()};
-	}
-
-	protected boolean acceptPush(Transport byTransport) {
-		if (byTransport == null) {
-			logger.info("Unknown transport, push rejected!");
-			return false;
-		}
-
-		Set<Transport> transports = new HashSet<Transport>();
-		for (String value : getSettings().getStrings(Keys.git.acceptedPushTransports)) {
-			Transport transport = Transport.fromString(value);
-			if (transport == null) {
-				logger.info(String.format("Ignoring unknown registered transport %s", value));
-				continue;
-			}
-
-			transports.add(transport);
-		}
-
-		if (transports.isEmpty()) {
-			// no transports are explicitly specified, all are acceptable
-			return true;
-		}
-
-		// verify that the transport is permitted
-		return transports.contains(byTransport);
-	}
-
-	/**
-	 * Returns a list of repository URLs and the user access permission.
-	 *
-	 * @param request
-	 * @param user
-	 * @param repository
-	 * @return a list of repository urls
-	 */
-	@Override
-	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
-		if (user == null) {
-			user = UserModel.ANONYMOUS;
-		}
-		String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
-
-		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
-
-		// http/https url
-		if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
-			AccessPermission permission = user.getRepositoryPermission(repository).permission;
-			if (permission.exceeds(AccessPermission.NONE)) {
-				Transport transport = Transport.fromString(request.getScheme());
-				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(transport)) {
-					// downgrade the repo permission for this transport
-					// because it is not an acceptable PUSH transport
-					permission = AccessPermission.CLONE;
-				}
-				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
-			}
-		}
-
-		// ssh daemon url
-		String sshDaemonUrl = servicesManager.getSshDaemonUrl(request, user, repository);
-		if (!StringUtils.isEmpty(sshDaemonUrl)) {
-			AccessPermission permission = user.getRepositoryPermission(repository).permission;
-			if (permission.exceeds(AccessPermission.NONE)) {
-				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.SSH)) {
-					// downgrade the repo permission for this transport
-					// because it is not an acceptable PUSH transport
-					permission = AccessPermission.CLONE;
-				}
-
-				list.add(new RepositoryUrl(sshDaemonUrl, permission));
-			}
-		}
-
-		// git daemon url
-		String gitDaemonUrl = servicesManager.getGitDaemonUrl(request, user, repository);
-		if (!StringUtils.isEmpty(gitDaemonUrl)) {
-			AccessPermission permission = servicesManager.getGitDaemonAccessPermission(user, repository);
-			if (permission.exceeds(AccessPermission.NONE)) {
-				if (permission.atLeast(AccessPermission.PUSH) && !acceptPush(Transport.GIT)) {
-					// downgrade the repo permission for this transport
-					// because it is not an acceptable PUSH transport
-					permission = AccessPermission.CLONE;
-				}
-				list.add(new RepositoryUrl(gitDaemonUrl, permission));
-			}
-		}
-
-		// add all other urls
-		// {0} = repository
-		// {1} = username
-		for (String url : settings.getStrings(Keys.web.otherUrls)) {
-			if (url.contains("{1}")) {
-				// external url requires username, only add url IF we have one
-				if (!StringUtils.isEmpty(username)) {
-					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
-				}
-			} else {
-				// external url does not require username
-				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
-			}
-		}
-
-		// sort transports by highest permission and then by transport security
-		Collections.sort(list, new Comparator<RepositoryUrl>() {
-
-			@Override
-			public int compare(RepositoryUrl o1, RepositoryUrl o2) {
-				if (!o1.isExternal() && o2.isExternal()) {
-					// prefer Gitblit over external
-					return -1;
-				} else if (o1.isExternal() && !o2.isExternal()) {
-					// prefer Gitblit over external
-					return 1;
-				} else if (o1.isExternal() && o2.isExternal()) {
-					// sort by Transport ordinal
-					return o1.transport.compareTo(o2.transport);
-				} else if (o1.permission.exceeds(o2.permission)) {
-					// prefer highest permission
-					return -1;
-				} else if (o2.permission.exceeds(o1.permission)) {
-					// prefer highest permission
-					return 1;
-				}
-
-				// prefer more secure transports
-				return o1.transport.compareTo(o2.transport);
-			}
-		});
-
-		// consider the user's transport preference
-		RepositoryUrl preferredUrl = null;
-		Transport preferredTransport = user.getPreferences().getTransport();
-		if (preferredTransport != null) {
-			Iterator<RepositoryUrl> itr = list.iterator();
-			while (itr.hasNext()) {
-				RepositoryUrl url = itr.next();
-				if (url.transport.equals(preferredTransport)) {
-					itr.remove();
-					preferredUrl = url;
-					break;
-				}
-			}
-		}
-		if (preferredUrl != null) {
-			list.add(0, preferredUrl);
-		}
-
-		return list;
-	}
-
-	/**
-	 * Detect renames and reindex as appropriate.
-	 */
-	@Override
-	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
-			boolean isCreate) throws GitBlitException {
-		RepositoryModel oldModel = null;
-		boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
-		if (isRename) {
-			oldModel = repositoryManager.getRepositoryModel(repositoryName);
-		}
-
-		super.updateRepositoryModel(repositoryName, repository, isCreate);
-
-		if (isRename && ticketService != null) {
-			ticketService.rename(oldModel, repository);
-		}
-	}
-
-	/**
-	 * Delete the user and all associated public ssh keys.
-	 */
-	@Override
-	public boolean deleteUser(String username) {
-		UserModel user = userManager.getUserModel(username);
-		return deleteUserModel(user);
-	}
-
-	@Override
-	public boolean deleteUserModel(UserModel model) {
-		boolean success = userManager.deleteUserModel(model);
-		if (success) {
-			getPublicKeyManager().removeAllKeys(model.username);
-		}
-		return success;
-	}
-
-	/**
-	 * Delete the repository and all associated tickets.
-	 */
-	@Override
-	public boolean deleteRepository(String repositoryName) {
-		RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
-		return deleteRepositoryModel(repository);
-	}
-
-	@Override
-	public boolean deleteRepositoryModel(RepositoryModel model) {
-		boolean success = repositoryManager.deleteRepositoryModel(model);
-		if (success && ticketService != null) {
-			ticketService.deleteAll(model);
-		}
-		return success;
-	}
-
-	/**
-	 * Returns the configured ticket service.
-	 *
-	 * @return a ticket service
-	 */
-	@Override
-	public ITicketService getTicketService() {
-		return ticketService;
-	}
-
-	protected void configureTicketService() {
-		String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName());
-		if (StringUtils.isEmpty(clazz)) {
-			clazz = NullTicketService.class.getName();
-		}
-		try {
-			Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz);
-			ticketService = injector.get(serviceClass).start();
-			if (ticketService instanceof NullTicketService) {
-				logger.warn("No ticket service configured.");
-			} else if (ticketService.isReady()) {
-				logger.info("{} is ready.", ticketService);
-			} else {
-				logger.warn("{} is disabled.", ticketService);
-			}
-		} catch (Exception e) {
-			logger.error("failed to create ticket service " + clazz, e);
-			ticketService = injector.get(NullTicketService.class).start();
-		}
-	}
-
-	/**
-	 * A nested Dagger graph is used for constructor dependency injection of
-	 * complex classes.
-	 *
-	 * @author James Moger
-	 *
-	 */
-	@Module(
-			library = true,
-			injects = {
-					IStoredSettings.class,
-
-					// core managers
-					IRuntimeManager.class,
-					IPluginManager.class,
-					INotificationManager.class,
-					IUserManager.class,
-					IAuthenticationManager.class,
-					IRepositoryManager.class,
-					IProjectManager.class,
-					IFederationManager.class,
-
-					// the monolithic manager
-					IGitblit.class,
-
-					// ticket services
-					NullTicketService.class,
-					FileTicketService.class,
-					BranchTicketService.class,
-					RedisTicketService.class
-				}
-			)
-	class GitBlitModule {
-
-		@Provides @Singleton IStoredSettings provideSettings() {
-			return settings;
-		}
-
-		@Provides @Singleton IRuntimeManager provideRuntimeManager() {
-			return runtimeManager;
-		}
-
-		@Provides @Singleton IPluginManager providePluginManager() {
-			return pluginManager;
-		}
-
-		@Provides @Singleton INotificationManager provideNotificationManager() {
-			return notificationManager;
-		}
-
-		@Provides @Singleton IUserManager provideUserManager() {
-			return userManager;
-		}
-
-		@Provides @Singleton IAuthenticationManager provideAuthenticationManager() {
-			return authenticationManager;
-		}
-
-		@Provides @Singleton IRepositoryManager provideRepositoryManager() {
-			return repositoryManager;
-		}
-
-		@Provides @Singleton IProjectManager provideProjectManager() {
-			return projectManager;
-		}
-
-		@Provides @Singleton IFederationManager provideFederationManager() {
-			return federationManager;
-		}
-
-		@Provides @Singleton IGitblit provideGitblit() {
-			return GitBlit.this;
-		}
-
-		@Provides @Singleton NullTicketService provideNullTicketService() {
-			return new NullTicketService(
-					runtimeManager,
-					pluginManager,
-					notificationManager,
-					userManager,
-					repositoryManager);
-		}
-
-		@Provides @Singleton FileTicketService provideFileTicketService() {
-			return new FileTicketService(
-					runtimeManager,
-					pluginManager,
-					notificationManager,
-					userManager,
-					repositoryManager);
-		}
-
-		@Provides @Singleton BranchTicketService provideBranchTicketService() {
-			return new BranchTicketService(
-					runtimeManager,
-					pluginManager,
-					notificationManager,
-					userManager,
-					repositoryManager);
-		}
-
-		@Provides @Singleton RedisTicketService provideRedisTicketService() {
-			return new RedisTicketService(
-					runtimeManager,
-					pluginManager,
-					notificationManager,
-					userManager,
-					repositoryManager);
-		}
+				federationManager,
+				filestoreManager);
 	}
 }
diff --git a/src/main/java/com/gitblit/GitBlitServer.java b/src/main/java/com/gitblit/GitBlitServer.java
index c79b172..d56d9c0 100644
--- a/src/main/java/com/gitblit/GitBlitServer.java
+++ b/src/main/java/com/gitblit/GitBlitServer.java
@@ -217,22 +217,7 @@
 		}
 
 		logger = LoggerFactory.getLogger(GitBlitServer.class);
-		logger.info(Constants.BORDER);
-		logger.info("            _____  _  _    _      _  _  _");
-		logger.info("           |  __ \\(_)| |  | |    | |(_)| |");
-		logger.info("           | |  \\/ _ | |_ | |__  | | _ | |_");
-		logger.info("           | | __ | || __|| '_ \\ | || || __|");
-		logger.info("           | |_\\ \\| || |_ | |_) || || || |_");
-		logger.info("            \\____/|_| \\__||_.__/ |_||_| \\__|");
-		int spacing = (Constants.BORDER.length() - Constants.getGitBlitVersion().length()) / 2;
-		StringBuilder sb = new StringBuilder();
-		while (spacing > 0) {
-			spacing--;
-			sb.append(' ');
-		}
-		logger.info(sb.toString() + Constants.getGitBlitVersion());
-		logger.info("");
-		logger.info(Constants.BORDER);
+		logger.info("\n" + Constants.getASCIIArt());
 
 		System.setProperty("java.awt.headless", "true");
 
@@ -524,22 +509,25 @@
 
 		@Override
 		public void run() {
-			logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
-			Socket accept;
-			try {
-				accept = socket.accept();
-				BufferedReader reader = new BufferedReader(new InputStreamReader(
-						accept.getInputStream()));
-				reader.readLine();
-				logger.info(Constants.BORDER);
-				logger.info("Stopping " + Constants.NAME);
-				logger.info(Constants.BORDER);
-				server.stop();
-				server.setStopAtShutdown(false);
-				accept.close();
-				socket.close();
-			} catch (Exception e) {
-				logger.warn("Failed to shutdown Jetty", e);
+			// Only run if the socket was able to be created (not already in use, failed to bind, etc.)
+			if (null != socket) {
+				logger.info("Shutdown Monitor listening on port " + socket.getLocalPort());
+				Socket accept;
+				try {
+					accept = socket.accept();
+					BufferedReader reader = new BufferedReader(new InputStreamReader(
+							accept.getInputStream()));
+					reader.readLine();
+					logger.info(Constants.BORDER);
+					logger.info("Stopping " + Constants.NAME);
+					logger.info(Constants.BORDER);
+					server.stop();
+					server.setStopAtShutdown(false);
+					accept.close();
+					socket.close();
+				} catch (Exception e) {
+					logger.warn("Failed to shutdown Jetty", e);
+				}
 			}
 		}
 	}
diff --git a/src/main/java/com/gitblit/GravatarGenerator.java b/src/main/java/com/gitblit/GravatarGenerator.java
new file mode 100644
index 0000000..1ba02e5
--- /dev/null
+++ b/src/main/java/com/gitblit/GravatarGenerator.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2015 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;
+
+import com.gitblit.utils.ActivityUtils;
+import com.google.inject.Singleton;
+
+@Singleton
+public class GravatarGenerator implements AvatarGenerator {
+
+	@Override
+	public String getURL(String username, String emailaddress, boolean identicon, int width) {
+		String email = emailaddress == null ? username : emailaddress;
+		if (identicon) {
+			return ActivityUtils.getGravatarIdenticonUrl(email, width);
+		} else {
+			return ActivityUtils.getGravatarThumbnailUrl(email, width);
+		}
+	}
+
+}
diff --git a/src/main/java/com/gitblit/auth/AuthenticationProvider.java b/src/main/java/com/gitblit/auth/AuthenticationProvider.java
index f6511ba..c56c184 100644
--- a/src/main/java/com/gitblit/auth/AuthenticationProvider.java
+++ b/src/main/java/com/gitblit/auth/AuthenticationProvider.java
@@ -22,6 +22,7 @@
 import org.slf4j.LoggerFactory;
 
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.IStoredSettings;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.manager.IUserManager;
@@ -151,6 +152,24 @@
 	 */
 	public abstract boolean supportsTeamMembershipChanges();
 
+	/**
+	 * Returns true if the user's role can be changed.
+	 *
+	 * @param user
+	 * @param role
+	 * @return true if the user's role can be changed
+	 */
+	public abstract boolean supportsRoleChanges(UserModel user, Role role);
+
+	/**
+	 * Returns true if the team's role can be changed.
+	 *
+	 * @param user
+	 * @param role
+	 * @return true if the team's role can be changed
+	 */
+	public abstract boolean supportsRoleChanges(TeamModel team, Role role);
+
     @Override
     public String toString() {
     	return getServiceName() + " (" + getClass().getName() + ")";
@@ -212,5 +231,16 @@
 		public boolean supportsTeamMembershipChanges() {
 			return true;
 		}
+
+		@Override
+		public boolean supportsRoleChanges(UserModel user, Role role) {
+			return true;
+		}
+
+		@Override
+		public boolean supportsRoleChanges(TeamModel team, Role role) {
+			return true;
+		}
+
     }
 }
diff --git a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
index 5ffb693..2cdabf6 100644
--- a/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/HtpasswdAuthProvider.java
@@ -32,8 +32,10 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 
 
@@ -124,6 +126,16 @@
         return true;
     }
 
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return true;
+	}
+
     /**
      * Authenticate a user based on a username and password.
      *
diff --git a/src/main/java/com/gitblit/auth/LdapAuthProvider.java b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
index 5690073..cc772e7 100644
--- a/src/main/java/com/gitblit/auth/LdapAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/LdapAuthProvider.java
@@ -30,6 +30,7 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
 import com.gitblit.models.TeamModel;
@@ -272,7 +273,6 @@
 		return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.email, ""));
 	}
 
-
 	/**
 	 * If the LDAP server will maintain team memberships then LdapUserService
 	 * will not allow team membership changes.  In this scenario all team
@@ -284,6 +284,32 @@
 	@Override
 	public boolean supportsTeamMembershipChanges() {
 		return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
+	}
+
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+    	if (Role.ADMIN == role) {
+    		if (!supportsTeamMembershipChanges()) {
+    			List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+    			if (admins.contains(user.username)) {
+    				return false;
+    			}
+    		}
+    	}
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		if (Role.ADMIN == role) {
+    		if (!supportsTeamMembershipChanges()) {
+    			List<String> admins = settings.getStrings(Keys.realm.ldap.admins);
+    			if (admins.contains("@" + team.name)) {
+    				return false;
+    			}
+    		}
+    	}
+		return true;
 	}
 
 	@Override
@@ -591,7 +617,8 @@
 		if (ldapSyncService.isReady()) {
 			long ldapSyncPeriod = getSynchronizationPeriodInMilliseconds();
 			int delay = 1;
-			logger.info("Ldap sync service will update users and groups every {} minutes.", ldapSyncPeriod);
+			logger.info("Ldap sync service will update users and groups every {} minutes.",
+					TimeUnit.MILLISECONDS.toMinutes(ldapSyncPeriod));
 			scheduledExecutorService.scheduleAtFixedRate(ldapSyncService, delay, ldapSyncPeriod,  TimeUnit.MILLISECONDS);
 		} else {
 			logger.info("Ldap sync service is disabled.");
diff --git a/src/main/java/com/gitblit/auth/PAMAuthProvider.java b/src/main/java/com/gitblit/auth/PAMAuthProvider.java
index 5d441b8..46f4dd6 100644
--- a/src/main/java/com/gitblit/auth/PAMAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/PAMAuthProvider.java
@@ -23,8 +23,10 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 
 /**
@@ -77,6 +79,16 @@
         return true;
     }
 
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return true;
+	}
+
 	 @Override
 	public AccountType getAccountType() {
 		return AccountType.PAM;
diff --git a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
index ae4f28e..27cece2 100644
--- a/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/RedmineAuthProvider.java
@@ -23,8 +23,10 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.ConnectionUtils;
 import com.gitblit.utils.StringUtils;
@@ -76,6 +78,16 @@
     public boolean supportsTeamMembershipChanges() {
         return false;
     }
+
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return true;
+	}
 
 	 @Override
 	public AccountType getAccountType() {
@@ -154,7 +166,7 @@
         	url = url.concat("/");
         }
         String apiUrl = url + "users/current.json";
-        
+
         HttpURLConnection http;
         if (username == null) {
         	// apikey authentication
diff --git a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
index e4273ff..df033c2 100644
--- a/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/SalesforceAuthProvider.java
@@ -2,8 +2,10 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.sforce.soap.partner.Connector;
 import com.sforce.soap.partner.GetUserInfoResult;
@@ -119,4 +121,15 @@
 	public boolean supportsTeamMembershipChanges() {
 		return true;
 	}
+
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return true;
+	}
+
 }
diff --git a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
index ac15b28..aee5100 100644
--- a/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
+++ b/src/main/java/com/gitblit/auth/WindowsAuthProvider.java
@@ -26,8 +26,10 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
+import com.gitblit.Constants.Role;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
+import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 import com.sun.jna.platform.win32.Win32Exception;
@@ -90,6 +92,16 @@
         return true;
     }
 
+    @Override
+    public boolean supportsRoleChanges(UserModel user, Role role) {
+        return true;
+    }
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return true;
+	}
+
 	 @Override
 	public AccountType getAccountType() {
 		return AccountType.WINDOWS;
diff --git a/src/main/java/com/gitblit/dagger/DaggerContext.java b/src/main/java/com/gitblit/dagger/DaggerContext.java
deleted file mode 100644
index 0e6a3fc..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerContext.java
+++ /dev/null
@@ -1,67 +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.dagger;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletContextEvent;
-import javax.servlet.ServletContextListener;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import dagger.ObjectGraph;
-
-/**
- * Dagger servlet context listener is a context listener that uses Dagger to
- * instantiate and inject servlets, filters, and anything else you might want.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerContext implements ServletContextListener {
-
-	public static final String INJECTOR_NAME = ObjectGraph.class.getName();
-
-	protected final Logger logger = LoggerFactory.getLogger(getClass());
-
-	protected abstract Object [] getModules();
-
-	protected abstract void destroyContext(ServletContext context);
-
-	protected ObjectGraph getInjector(ServletContext context) {
-		Object o = context.getAttribute(INJECTOR_NAME);
-		if (o == null) {
-			logger.debug("instantiating Dagger modules");
-			Object [] modules = getModules();
-			logger.debug("getting Dagger injector");
-			try {
-				o = ObjectGraph.create(modules);
-				logger.debug("setting Dagger injector into {} attribute", INJECTOR_NAME);
-				context.setAttribute(INJECTOR_NAME, o);
-			} catch (Throwable t) {
-				logger.error("an error occurred creating the Dagger injector", t);
-			}
-		}
-		return (ObjectGraph) o;
-	}
-
-	@Override
-	public final void contextDestroyed(ServletContextEvent contextEvent) {
-		ServletContext context = contextEvent.getServletContext();
-		context.removeAttribute(INJECTOR_NAME);
-		destroyContext(context);
-	}
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerFilter.java b/src/main/java/com/gitblit/dagger/DaggerFilter.java
deleted file mode 100644
index 01c07a4..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerFilter.java
+++ /dev/null
@@ -1,47 +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.dagger;
-
-import javax.servlet.Filter;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a servlet filter.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerFilter implements Filter {
-
-	@Override
-	public final void init(FilterConfig filterConfig) throws ServletException {
-		ServletContext context = filterConfig.getServletContext();
-		ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
-		inject(objectGraph, filterConfig);
-	}
-
-	protected abstract void inject(ObjectGraph dagger, FilterConfig filterConfig) throws ServletException;
-
-	@Override
-	public void destroy() {
-	}
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerServlet.java b/src/main/java/com/gitblit/dagger/DaggerServlet.java
deleted file mode 100644
index 88331a4..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerServlet.java
+++ /dev/null
@@ -1,44 +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.dagger;
-
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a servlet.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerServlet extends HttpServlet {
-
-	private static final long serialVersionUID = 1L;
-
-	@Override
-	public final void init() throws ServletException {
-		ServletContext context = getServletContext();
-		ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
-		inject(objectGraph);
-	}
-
-	protected abstract void inject(ObjectGraph dagger);
-}
diff --git a/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java b/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java
deleted file mode 100644
index c2fd4d6..0000000
--- a/src/main/java/com/gitblit/dagger/DaggerWicketFilter.java
+++ /dev/null
@@ -1,45 +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.dagger;
-
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletContext;
-import javax.servlet.ServletException;
-
-import org.apache.wicket.protocol.http.WicketFilter;
-
-import dagger.ObjectGraph;
-
-/**
- * Uses Dagger to manually inject dependencies into a Wicket filter.
- * This class is useful for servlet containers that offer CDI and are
- * confused by Dagger.
- *
- * @author James Moger
- *
- */
-public abstract class DaggerWicketFilter extends WicketFilter {
-
-	@Override
-	public final void init(FilterConfig filterConfig) throws ServletException {
-		ServletContext context = filterConfig.getServletContext();
-		ObjectGraph objectGraph = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
-		inject(objectGraph);
-		super.init(filterConfig);
-	}
-
-	protected abstract void inject(ObjectGraph dagger);
-}
diff --git a/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java b/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java
index 5ef03af..e23fca7 100644
--- a/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java
+++ b/src/main/java/com/gitblit/extensions/RepositoryLifeCycleListener.java
@@ -36,6 +36,24 @@
 	public abstract void onCreation(RepositoryModel repository);
 
 	/**
+	 * Called after a repository has been forked.
+	 *
+	 * @param origin
+	 * @param fork
+	 * @since 1.7.0
+	 */
+	public abstract void onFork(RepositoryModel origin, RepositoryModel fork);
+
+	/**
+	 * Called after a repository has been renamed.
+	 *
+	 * @param oldName
+	 * @param repository
+	 * @since 1.7.0
+	 */
+	public abstract void onRename(String oldName, RepositoryModel repository);
+
+	/**
 	 * Called after a repository has been deleted.
 	 *
 	 * @param repository
diff --git a/src/main/java/com/gitblit/git/PatchsetReceivePack.java b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
index 54ffb7b..ef0b409 100644
--- a/src/main/java/com/gitblit/git/PatchsetReceivePack.java
+++ b/src/main/java/com/gitblit/git/PatchsetReceivePack.java
@@ -667,7 +667,7 @@
 					// identified the missing object earlier before we got control.
 					LOGGER.error("failed to get commit count", e);
 				} finally {
-					walk.release();
+					walk.close();
 				}
 
 				sendError("");
@@ -1078,7 +1078,7 @@
 			LOGGER.error("failed to get commit count", e);
 			return 0;
 		} finally {
-			walk.release();
+			walk.close();
 		}
 		return count;
 	}
diff --git a/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java b/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java
new file mode 100644
index 0000000..a18e299
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/AvatarGeneratorProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2015 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.guice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.AvatarGenerator;
+import com.gitblit.GravatarGenerator;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated AvatarGenerator configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class AvatarGeneratorProvider implements Provider<AvatarGenerator> {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final IRuntimeManager runtimeManager;
+
+	private volatile AvatarGenerator avatarGenerator;
+
+	@Inject
+	public AvatarGeneratorProvider(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
+	}
+
+	@Override
+	public synchronized AvatarGenerator get() {
+		if (avatarGenerator != null) {
+			return avatarGenerator;
+		}
+
+		IStoredSettings settings = runtimeManager.getSettings();
+		String clazz = settings.getString(Keys.web.avatarClass, GravatarGenerator.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = GravatarGenerator.class.getName();
+		}
+		try {
+			Class<? extends AvatarGenerator> generatorClass = (Class<? extends AvatarGenerator>) Class.forName(clazz);
+			avatarGenerator = runtimeManager.getInjector().getInstance(generatorClass);
+		} catch (Exception e) {
+			logger.error("failed to create avatar generator", e);
+			avatarGenerator = new GravatarGenerator();
+		}
+		return avatarGenerator;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/CoreModule.java b/src/main/java/com/gitblit/guice/CoreModule.java
new file mode 100644
index 0000000..e2d1439
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/CoreModule.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2014 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.guice;
+
+import com.gitblit.FileSettings;
+import com.gitblit.GitBlit;
+import com.gitblit.IStoredSettings;
+import com.gitblit.manager.AuthenticationManager;
+import com.gitblit.manager.FederationManager;
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
+import com.gitblit.manager.IProjectManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.PluginManager;
+import com.gitblit.manager.ProjectManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.ServicesManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.utils.JSoupXssFilter;
+import com.gitblit.utils.WorkQueue;
+import com.gitblit.utils.XssFilter;
+import com.google.inject.AbstractModule;
+
+/**
+ * CoreModule references all the core business objects.
+ *
+ * @author James Moger
+ *
+ */
+public class CoreModule extends AbstractModule {
+
+	@Override
+	protected void configure() {
+
+		bind(IStoredSettings.class).toInstance(new FileSettings());
+		bind(XssFilter.class).to(JSoupXssFilter.class);
+
+		// bind complex providers
+		bind(IPublicKeyManager.class).toProvider(IPublicKeyManagerProvider.class);
+		bind(ITicketService.class).toProvider(ITicketServiceProvider.class);
+		bind(WorkQueue.class).toProvider(WorkQueueProvider.class);
+
+		// core managers
+		bind(IRuntimeManager.class).to(RuntimeManager.class);
+		bind(IPluginManager.class).to(PluginManager.class);
+		bind(INotificationManager.class).to(NotificationManager.class);
+		bind(IUserManager.class).to(UserManager.class);
+		bind(IAuthenticationManager.class).to(AuthenticationManager.class);
+		bind(IRepositoryManager.class).to(RepositoryManager.class);
+		bind(IProjectManager.class).to(ProjectManager.class);
+		bind(IFederationManager.class).to(FederationManager.class);
+		bind(IFilestoreManager.class).to(FilestoreManager.class);
+
+		// the monolithic manager
+		bind(IGitblit.class).to(GitBlit.class);
+
+		// manager for long-running daemons and services
+		bind(IServicesManager.class).to(ServicesManager.class);
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java b/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java
new file mode 100644
index 0000000..8075aa9
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/IPublicKeyManagerProvider.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2014 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.guice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.transport.ssh.FileKeyManager;
+import com.gitblit.transport.ssh.IPublicKeyManager;
+import com.gitblit.transport.ssh.NullKeyManager;
+import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated IPublicKeyManager configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class IPublicKeyManagerProvider implements Provider<IPublicKeyManager> {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final IRuntimeManager runtimeManager;
+
+	private volatile IPublicKeyManager manager;
+
+	@Inject
+	public IPublicKeyManagerProvider(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
+	}
+
+	@Override
+	public synchronized IPublicKeyManager get() {
+		if (manager != null) {
+			return manager;
+		}
+
+		IStoredSettings settings = runtimeManager.getSettings();
+		String clazz = settings.getString(Keys.git.sshKeysManager, FileKeyManager.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = FileKeyManager.class.getName();
+		}
+		try {
+			Class<? extends IPublicKeyManager> mgrClass = (Class<? extends IPublicKeyManager>) Class.forName(clazz);
+			manager = runtimeManager.getInjector().getInstance(mgrClass);
+		} catch (Exception e) {
+			logger.error("failed to create public key manager", e);
+			manager = new NullKeyManager();
+		}
+		return manager;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/ITicketServiceProvider.java b/src/main/java/com/gitblit/guice/ITicketServiceProvider.java
new file mode 100644
index 0000000..fd39955
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/ITicketServiceProvider.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2014 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.guice;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.NullTicketService;
+import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated ITicketService configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class ITicketServiceProvider implements Provider<ITicketService> {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final IRuntimeManager runtimeManager;
+
+	private volatile ITicketService service;
+
+	@Inject
+	public ITicketServiceProvider(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
+	}
+
+	@Override
+	public synchronized ITicketService get() {
+		if (service != null) {
+			return service;
+		}
+
+		IStoredSettings settings = runtimeManager.getSettings();
+		String clazz = settings.getString(Keys.tickets.service, NullTicketService.class.getName());
+		if (StringUtils.isEmpty(clazz)) {
+			clazz = NullTicketService.class.getName();
+		}
+
+		try {
+			Class<? extends ITicketService> serviceClass = (Class<? extends ITicketService>) Class.forName(clazz);
+			service = runtimeManager.getInjector().getInstance(serviceClass);
+		} catch (Exception e) {
+			logger.error("failed to create ticket service", e);
+			service = runtimeManager.getInjector().getInstance(NullTicketService.class);
+		}
+
+		return service;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/guice/WebModule.java b/src/main/java/com/gitblit/guice/WebModule.java
new file mode 100644
index 0000000..7c83e45
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/WebModule.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2014 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.guice;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.gitblit.AvatarGenerator;
+import com.gitblit.Constants;
+import com.gitblit.servlet.AccessDeniedServlet;
+import com.gitblit.servlet.BranchGraphServlet;
+import com.gitblit.servlet.DownloadZipFilter;
+import com.gitblit.servlet.DownloadZipServlet;
+import com.gitblit.servlet.EnforceAuthenticationFilter;
+import com.gitblit.servlet.FederationServlet;
+import com.gitblit.servlet.FilestoreServlet;
+import com.gitblit.servlet.GitFilter;
+import com.gitblit.servlet.GitServlet;
+import com.gitblit.servlet.LogoServlet;
+import com.gitblit.servlet.PagesFilter;
+import com.gitblit.servlet.PagesServlet;
+import com.gitblit.servlet.ProxyFilter;
+import com.gitblit.servlet.PtServlet;
+import com.gitblit.servlet.RawFilter;
+import com.gitblit.servlet.RawServlet;
+import com.gitblit.servlet.RobotsTxtServlet;
+import com.gitblit.servlet.RpcFilter;
+import com.gitblit.servlet.RpcServlet;
+import com.gitblit.servlet.SparkleShareInviteServlet;
+import com.gitblit.servlet.SyndicationFilter;
+import com.gitblit.servlet.SyndicationServlet;
+import com.gitblit.wicket.GitblitWicketFilter;
+import com.google.common.base.Joiner;
+import com.google.inject.servlet.ServletModule;
+
+/**
+ * Defines all the web servlets & filters.
+ *
+ * @author James Moger
+ *
+ */
+public class WebModule extends ServletModule {
+
+	final static String ALL = "/*";
+
+	@Override
+	protected void configureServlets() {
+
+		// bind web component providers
+		bind(AvatarGenerator.class).toProvider(AvatarGeneratorProvider.class);
+
+		// servlets
+		serveRegex(FilestoreServlet.REGEX_PATH).with(FilestoreServlet.class);
+		serve(fuzzy(Constants.R_PATH), fuzzy(Constants.GIT_PATH)).with(GitServlet.class);
+		serve(fuzzy(Constants.RAW_PATH)).with(RawServlet.class);
+		serve(fuzzy(Constants.PAGES)).with(PagesServlet.class);
+		serve(fuzzy(Constants.RPC_PATH)).with(RpcServlet.class);
+		serve(fuzzy(Constants.ZIP_PATH)).with(DownloadZipServlet.class);
+		serve(fuzzy(Constants.SYNDICATION_PATH)).with(SyndicationServlet.class);
+		
+
+		serve(fuzzy(Constants.FEDERATION_PATH)).with(FederationServlet.class);
+		serve(fuzzy(Constants.SPARKLESHARE_INVITE_PATH)).with(SparkleShareInviteServlet.class);
+		serve(fuzzy(Constants.BRANCH_GRAPH_PATH)).with(BranchGraphServlet.class);
+		serve(Constants.PT_PATH).with(PtServlet.class);
+		serve("/robots.txt").with(RobotsTxtServlet.class);
+		serve("/logo.png").with(LogoServlet.class);
+
+		/* Prevent accidental access to 'resources' such as GitBlit java classes
+		 *
+		 * In the GO setup the JAR containing the application and the WAR injected
+		 * into Jetty are the same file. However Jetty expects to serve the entire WAR
+		 * contents, except the WEB-INF folder. Thus, all java binary classes in the
+		 * JAR are served by default as is they were legitimate resources.
+		 *
+		 * The below servlet mappings prevent that behavior
+		 */
+		serve(fuzzy("/com/")).with(AccessDeniedServlet.class);
+
+		// global filters
+		filter(ALL).through(ProxyFilter.class);
+		filter(ALL).through(EnforceAuthenticationFilter.class);
+
+		// security filters
+		filter(fuzzy(Constants.R_PATH), fuzzy(Constants.GIT_PATH)).through(GitFilter.class);
+		filter(fuzzy(Constants.RAW_PATH)).through(RawFilter.class);
+		filter(fuzzy(Constants.PAGES)).through(PagesFilter.class);
+		filter(fuzzy(Constants.RPC_PATH)).through(RpcFilter.class);
+		filter(fuzzy(Constants.ZIP_PATH)).through(DownloadZipFilter.class);
+		filter(fuzzy(Constants.SYNDICATION_PATH)).through(SyndicationFilter.class);
+		
+		
+		// Wicket
+		String toIgnore = Joiner.on(",").join(Constants.R_PATH, Constants.GIT_PATH, Constants.RAW_PATH,
+				Constants.PAGES, Constants.RPC_PATH, Constants.ZIP_PATH, Constants.SYNDICATION_PATH,
+				Constants.FEDERATION_PATH, Constants.SPARKLESHARE_INVITE_PATH, Constants.BRANCH_GRAPH_PATH,
+				Constants.PT_PATH, "/robots.txt", "/logo.png");
+
+		Map<String, String> params = new HashMap<String, String>();
+		params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, ALL);
+		params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
+		filter(ALL).through(GitblitWicketFilter.class, params);
+	}
+
+	private String fuzzy(String path) {
+		if (path.endsWith(ALL)) {
+			return path;
+		} else if (path.endsWith("/")) {
+			return path + "*";
+		}
+		return path + ALL;
+	}
+}
diff --git a/src/main/java/com/gitblit/guice/WorkQueueProvider.java b/src/main/java/com/gitblit/guice/WorkQueueProvider.java
new file mode 100644
index 0000000..cde27ea
--- /dev/null
+++ b/src/main/java/com/gitblit/guice/WorkQueueProvider.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2014 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.guice;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.IdGenerator;
+import com.gitblit.utils.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+/**
+ * Provides a lazily-instantiated WorkQueue configured from IStoredSettings.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class WorkQueueProvider implements Provider<WorkQueue> {
+
+	private final IRuntimeManager runtimeManager;
+
+	private volatile WorkQueue workQueue;
+
+	@Inject
+	public WorkQueueProvider(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
+	}
+
+	@Override
+	public synchronized WorkQueue get() {
+		if (workQueue != null) {
+			return workQueue;
+		}
+
+		IStoredSettings settings = runtimeManager.getSettings();
+		int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1);
+		IdGenerator idGenerator = new IdGenerator();
+		workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize);
+		return workQueue;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/manager/AuthenticationManager.java b/src/main/java/com/gitblit/manager/AuthenticationManager.java
index f98f7b6..7e0b07b 100644
--- a/src/main/java/com/gitblit/manager/AuthenticationManager.java
+++ b/src/main/java/com/gitblit/manager/AuthenticationManager.java
@@ -35,6 +35,7 @@
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccountType;
 import com.gitblit.Constants.AuthenticationType;
+import com.gitblit.Constants.Role;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.auth.AuthenticationProvider;
@@ -52,6 +53,8 @@
 import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.X509Utils.X509Metadata;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * The authentication manager handles user login & logout.
@@ -59,6 +62,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class AuthenticationManager implements IAuthenticationManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -75,6 +79,7 @@
 
 	private final Map<String, String> legacyRedirects;
 
+	@Inject
 	public AuthenticationManager(
 			IRuntimeManager runtimeManager,
 			IUserManager userManager) {
@@ -189,6 +194,14 @@
 	 */
 	@Override
 	public UserModel authenticate(HttpServletRequest httpRequest, boolean requiresCertificate) {
+
+		// Check if this request has already been authenticated, and trust that instead of re-processing
+		String reqAuthUser = (String) httpRequest.getAttribute(Constants.ATTRIB_AUTHUSER);
+		if (!StringUtils.isEmpty(reqAuthUser)) {
+			logger.warn("Called servlet authenticate when request is already authenticated.");
+			return userManager.getUserModel(reqAuthUser);
+		}
+
 		// try to authenticate by servlet container principal
 		if (!requiresCertificate) {
 			Principal principal = httpRequest.getUserPrincipal();
@@ -199,7 +212,7 @@
 					UserModel user = userManager.getUserModel(username);
 					if (user != null) {
 						// existing user
-						flagSession(httpRequest, AuthenticationType.CONTAINER);
+						flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username);
 						logger.debug(MessageFormat.format("{0} authenticated by servlet container principal from {1}",
 								user.username, httpRequest.getRemoteAddr()));
 						return validateAuthentication(user, AuthenticationType.CONTAINER);
@@ -210,8 +223,31 @@
 						user.displayName = username;
 						user.password = Constants.EXTERNAL_ACCOUNT;
 						user.accountType = AccountType.CONTAINER;
+						
+						// Try to extract user's informations for the session
+						// it uses "realm.container.autoAccounts.*" as the attribute name to look for
+						HttpSession session = httpRequest.getSession();
+						String emailAddress = resolveAttribute(session, Keys.realm.container.autoAccounts.emailAddress);
+						if(emailAddress != null) {
+							user.emailAddress = emailAddress;
+						}
+						String displayName = resolveAttribute(session, Keys.realm.container.autoAccounts.displayName);
+						if(displayName != null) {
+							user.displayName = displayName;
+						}
+						String userLocale = resolveAttribute(session, Keys.realm.container.autoAccounts.locale);
+						if(userLocale != null) {
+							user.getPreferences().setLocale(userLocale);
+						}
+						String adminRole = settings.getString(Keys.realm.container.autoAccounts.adminRole, null);
+						if(adminRole != null && ! adminRole.isEmpty()) {
+							if(httpRequest.isUserInRole(adminRole)) {
+								user.canAdmin = true;
+							}
+						}
+						
 						userManager.updateUserModel(user);
-						flagSession(httpRequest, AuthenticationType.CONTAINER);
+						flagRequest(httpRequest, AuthenticationType.CONTAINER, user.username);
 						logger.debug(MessageFormat.format("{0} authenticated and created by servlet container principal from {1}",
 								user.username, httpRequest.getRemoteAddr()));
 						return validateAuthentication(user, AuthenticationType.CONTAINER);
@@ -232,7 +268,7 @@
 			UserModel user = userManager.getUserModel(model.username);
 			X509Metadata metadata = HttpUtils.getCertificateMetadata(httpRequest);
 			if (user != null) {
-				flagSession(httpRequest, AuthenticationType.CERTIFICATE);
+				flagRequest(httpRequest, AuthenticationType.CERTIFICATE, user.username);
 				logger.debug(MessageFormat.format("{0} authenticated by client certificate {1} from {2}",
 						user.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
 				return validateAuthentication(user, AuthenticationType.CERTIFICATE);
@@ -254,7 +290,7 @@
 		if (!StringUtils.isEmpty(cookie)) {
 			user = userManager.getUserModel(cookie.toCharArray());
 			if (user != null) {
-				flagSession(httpRequest, AuthenticationType.COOKIE);
+				flagRequest(httpRequest, AuthenticationType.COOKIE, user.username);
 				logger.debug(MessageFormat.format("{0} authenticated by cookie from {1}",
 					user.username, httpRequest.getRemoteAddr()));
 				return validateAuthentication(user, AuthenticationType.COOKIE);
@@ -274,19 +310,41 @@
 			if (values.length == 2) {
 				String username = values[0];
 				char[] password = values[1].toCharArray();
-				user = authenticate(username, password);
+				user = authenticate(username, password, httpRequest.getRemoteAddr());
 				if (user != null) {
-					flagSession(httpRequest, AuthenticationType.CREDENTIALS);
+					flagRequest(httpRequest, AuthenticationType.CREDENTIALS, user.username);
 					logger.debug(MessageFormat.format("{0} authenticated by BASIC request header from {1}",
 							user.username, httpRequest.getRemoteAddr()));
 					return validateAuthentication(user, AuthenticationType.CREDENTIALS);
-				} else {
-					logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
-							username, httpRequest.getRemoteAddr()));
 				}
 			}
 		}
 		return null;
+	}
+	
+	/**
+	 * Extract given attribute from the session and return it's content
+	 * it return null if attributeMapping is empty, or if the value is
+	 * empty
+	 * 
+	 * @param session The user session
+	 * @param attributeMapping
+	 * @return
+	 */
+	private String resolveAttribute(HttpSession session, String attributeMapping) {
+		String attributeName = settings.getString(attributeMapping, null);
+		if(StringUtils.isEmpty(attributeName)) {
+			return null;
+		}
+		Object attributeValue = session.getAttribute(attributeName);
+		if(attributeValue == null) {
+			return null;
+		}
+		String value = attributeValue.toString();
+		if(value.isEmpty()) {
+			return null;
+		}
+		return value;
 	}
 
 	/**
@@ -321,6 +379,35 @@
 
 
 	/**
+	 * Return the UserModel for already authenticated user.
+	 *
+	 * This implementation assumes that the authentication has already take place
+	 * (e.g. SSHDaemon) and that this is a validation/verification of the user.
+	 *
+	 * @param username
+	 * @return a user object or null
+	 */
+	@Override
+	public UserModel authenticate(String username) {
+		if (username != null) {
+			if (!StringUtils.isEmpty(username)) {
+				UserModel user = userManager.getUserModel(username);
+				if (user != null) {
+					// existing user
+					logger.debug(MessageFormat.format("{0} authenticated externally", user.username));
+					return validateAuthentication(user, AuthenticationType.CONTAINER);
+				}
+				logger.warn(MessageFormat.format("Failed to find UserModel for {0} during external authentication",
+							username));
+			}
+		} else {
+			logger.warn("Empty user passed to AuthenticationManager.authenticate!");
+		}
+		return null;
+	}
+
+
+	/**
 	 * This method allows the authentication manager to reject authentication
 	 * attempts.  It is called after the username/secret have been verified to
 	 * ensure that the authentication technique has been logged.
@@ -341,8 +428,9 @@
 		return user;
 	}
 
-	protected void flagSession(HttpServletRequest httpRequest, AuthenticationType authenticationType) {
-		httpRequest.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, authenticationType);
+	protected void flagRequest(HttpServletRequest httpRequest, AuthenticationType authenticationType, String authedUsername) {
+		httpRequest.setAttribute(Constants.ATTRIB_AUTHUSER,  authedUsername);
+		httpRequest.setAttribute(Constants.ATTRIB_AUTHTYPE,  authenticationType);
 	}
 
 	/**
@@ -354,7 +442,7 @@
 	 * @return a user object or null
 	 */
 	@Override
-	public UserModel authenticate(String username, char[] password) {
+	public UserModel authenticate(String username, char[] password, String remoteIP) {
 		if (StringUtils.isEmpty(username)) {
 			// can not authenticate empty username
 			return null;
@@ -371,22 +459,29 @@
 
 		// try local authentication
 		if (user != null && user.isLocalAccount()) {
-			return authenticateLocal(user, password);
-		}
-
-		// try registered external authentication providers
-		for (AuthenticationProvider provider : authenticationProviders) {
-			if (provider instanceof UsernamePasswordAuthenticationProvider) {
-				UserModel returnedUser = provider.authenticate(usernameDecoded, password);
-				if (returnedUser != null) {
-					// user authenticated
-					returnedUser.accountType = provider.getAccountType();
-					return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
+			UserModel returnedUser = authenticateLocal(user, password);
+			if (returnedUser != null) {
+				// user authenticated
+				return returnedUser;
+			}
+		} else {
+			// try registered external authentication providers
+			for (AuthenticationProvider provider : authenticationProviders) {
+				if (provider instanceof UsernamePasswordAuthenticationProvider) {
+					UserModel returnedUser = provider.authenticate(usernameDecoded, password);
+					if (returnedUser != null) {
+						// user authenticated
+						returnedUser.accountType = provider.getAccountType();
+						return validateAuthentication(returnedUser, AuthenticationType.CREDENTIALS);
+					}
 				}
 			}
 		}
 
 		// could not authenticate locally or with a provider
+		logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}", username, 
+				remoteIP != null ? remoteIP : "unknown"));
+		
 		return null;
 	}
 
@@ -463,9 +558,15 @@
 	@Override
 	public void setCookie(HttpServletRequest request, HttpServletResponse response, UserModel user) {
 		if (settings.getBoolean(Keys.web.allowCookieAuthentication, true)) {
-			HttpSession session = request.getSession();
-			AuthenticationType authenticationType = (AuthenticationType) session.getAttribute(Constants.AUTHENTICATION_TYPE);
-			boolean standardLogin = authenticationType.isStandard();
+			boolean standardLogin = true;
+
+			if (null != request) {
+				// Pull the auth type from the request, it is set there if container managed
+				AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
+
+				if (null != authenticationType)
+					standardLogin = authenticationType.isStandard();
+			}
 
 			if (standardLogin) {
 				Cookie userCookie;
@@ -576,6 +677,28 @@
 		return (team != null && team.isLocalTeam()) || findProvider(team).supportsTeamMembershipChanges();
 	}
 
+	/**
+	 * Returns true if the user's role can be changed.
+	 *
+	 * @param user
+	 * @return true if the user's role can be changed
+	 */
+	@Override
+	public boolean supportsRoleChanges(UserModel user, Role role) {
+		return (user != null && user.isLocalAccount()) || findProvider(user).supportsRoleChanges(user, role);
+	}
+
+	/**
+	 * Returns true if the team's role can be changed.
+	 *
+	 * @param user
+	 * @return true if the team's role can be changed
+	 */
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return (team != null && team.isLocalTeam()) || findProvider(team).supportsRoleChanges(team, role);
+	}
+
 	protected AuthenticationProvider findProvider(UserModel user) {
 		for (AuthenticationProvider provider : authenticationProviders) {
 			if (provider.getAccountType().equals(user.accountType)) {
diff --git a/src/main/java/com/gitblit/manager/FederationManager.java b/src/main/java/com/gitblit/manager/FederationManager.java
index 95d38af..f009c1c 100644
--- a/src/main/java/com/gitblit/manager/FederationManager.java
+++ b/src/main/java/com/gitblit/manager/FederationManager.java
@@ -45,6 +45,8 @@
 import com.gitblit.utils.FederationUtils;
 import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Federation manager controls all aspects of handling federation sets, tokens,
@@ -53,6 +55,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class FederationManager implements IFederationManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -70,6 +73,7 @@
 
 	private final IRepositoryManager repositoryManager;
 
+	@Inject
 	public FederationManager(
 			IRuntimeManager runtimeManager,
 			INotificationManager notificationManager,
diff --git a/src/main/java/com/gitblit/manager/FilestoreManager.java b/src/main/java/com/gitblit/manager/FilestoreManager.java
new file mode 100644
index 0000000..c15de5b
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/FilestoreManager.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright 2015 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.manager;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.IOUtils;
+import org.bouncycastle.util.io.StreamOverflowException;
+import org.eclipse.jetty.io.EofException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.JsonUtils.GmtDateTypeAdapter;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * FilestoreManager handles files uploaded via:
+ * 	+ git-lfs
+ *  + ticket attachment (TBD)
+ * 
+ * Files are stored using their SHA256 hash (as per git-lfs)
+ * If the same file is uploaded through different repositories no additional space is used
+ * Access is controlled through the current repository permissions.
+ *
+ * TODO: Identify what and how the actual BLOBs should work with federation
+ *
+ * @author Paul Martin
+ *
+ */
+@Singleton
+public class FilestoreManager implements IFilestoreManager {
+
+	private final Logger logger = LoggerFactory.getLogger(getClass());
+	
+	private final IRuntimeManager runtimeManager;
+	
+	private final IStoredSettings settings;
+	
+	public static final int UNDEFINED_SIZE = -1;
+	
+	private static final String METAFILE = "filestore.json";
+	
+	private static final String METAFILE_TMP = "filestore.json.tmp";
+	
+	protected static final Type METAFILE_TYPE = new TypeToken<Collection<FilestoreModel>>() {}.getType();
+	
+	private Map<String, FilestoreModel > fileCache = new ConcurrentHashMap<String, FilestoreModel>();
+	
+	
+	@Inject
+	FilestoreManager(
+			IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
+		this.settings = runtimeManager.getSettings();
+	}
+	
+	@Override
+	public IManager start() {
+
+		// Try to load any existing metadata
+		File dir = getStorageFolder();
+		dir.mkdirs();
+		File metadata = new File(dir, METAFILE);
+		
+		if (metadata.exists()) {
+			Collection<FilestoreModel> items = null;
+			
+			Gson gson = gson();
+			try (FileReader file = new FileReader(metadata)) {
+				items = gson.fromJson(file, METAFILE_TYPE);
+				file.close();
+				
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+			
+			for(Iterator<FilestoreModel> itr = items.iterator(); itr.hasNext(); ) {
+			    FilestoreModel model = itr.next();
+			    fileCache.put(model.oid, model);
+			}
+
+			logger.info("Loaded {} items from filestore metadata file", fileCache.size());
+		}
+		else
+		{
+			logger.info("No filestore metadata file found");
+		}
+		
+		return this;
+	}
+
+	@Override
+	public IManager stop() {
+		return this;
+	}
+
+
+	@Override
+	public boolean isValidOid(String oid) {
+		//NOTE: Assuming SHA256 support only as per git-lfs
+		return Pattern.matches("[a-fA-F0-9]{64}", oid);
+	}
+	
+	@Override
+	public FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo) {
+
+		//Handle access control
+		if (!user.canPush(repo)) {
+			if (user == UserModel.ANONYMOUS) {
+				return Status.AuthenticationRequired;
+			} else {
+				return Status.Error_Unauthorized;
+			}
+		}
+		
+		//Handle object details
+		if (!isValidOid(oid)) { return Status.Error_Invalid_Oid; }
+		
+		if (fileCache.containsKey(oid)) {
+			FilestoreModel item = fileCache.get(oid);
+			
+			if (!item.isInErrorState() && (size != UNDEFINED_SIZE) && (item.getSize() != size)) {
+				return Status.Error_Size_Mismatch;
+			}
+			
+			item.addRepository(repo.name);
+			
+			if (item.isInErrorState()) {
+				item.reset(user, size);
+			}
+		} else {
+			
+			if (size  < 0) {return Status.Error_Invalid_Size; }
+			if ((getMaxUploadSize() != UNDEFINED_SIZE) && (size > getMaxUploadSize())) { return Status.Error_Exceeds_Size_Limit; }
+			
+			FilestoreModel model = new FilestoreModel(oid, size, user, repo.name); 
+			fileCache.put(oid, model);
+			saveFilestoreModel(model);
+		}
+		
+		return fileCache.get(oid).getStatus();
+	}
+
+	@Override
+	public FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn) {
+
+		//Access control and object logic
+		Status state = addObject(oid, size, user, repo); 
+		
+		if (state != Status.Upload_Pending) { 
+			return state;
+		}
+		
+		FilestoreModel model = fileCache.get(oid);
+		
+		if (!model.actionUpload(user)) {
+			return Status.Upload_In_Progress;
+		} else {
+			long actualSize = 0;
+			File file = getStoragePath(oid);
+
+			try {
+				file.getParentFile().mkdirs();
+				file.createNewFile();
+					
+				try (FileOutputStream streamOut = new FileOutputStream(file)) {
+					
+					actualSize = IOUtils.copyLarge(streamIn, streamOut);
+					
+					streamOut.flush();
+					streamOut.close();
+					
+					if (model.getSize() != actualSize) {
+						model.setStatus(Status.Error_Size_Mismatch, user);
+						
+						logger.warn(MessageFormat.format("Failed to upload blob {0} due to size mismatch, expected {1} got {2}", 
+								oid, model.getSize(), actualSize));
+					} else {
+						String actualOid = "";
+						
+						try (FileInputStream fileForHash = new FileInputStream(file)) {
+							actualOid = DigestUtils.sha256Hex(fileForHash);
+							fileForHash.close();
+						}
+						
+						if (oid.equalsIgnoreCase(actualOid)) {
+							model.setStatus(Status.Available, user);
+						} else {
+							model.setStatus(Status.Error_Hash_Mismatch, user);
+							
+							logger.warn(MessageFormat.format("Failed to upload blob {0} due to hash mismatch, got {1}", oid, actualOid));
+						}
+					}
+				}
+			} catch (Exception e) {
+				
+				model.setStatus(Status.Error_Unknown, user);
+				logger.warn(MessageFormat.format("Failed to upload blob {0}", oid), e);
+			} finally {
+				saveFilestoreModel(model);
+			}
+			
+			if (model.isInErrorState()) {
+				file.delete();
+				model.removeRepository(repo.name);
+			}
+		}
+		
+		return model.getStatus();
+	}
+	
+	private FilestoreModel.Status canGetObject(String oid, UserModel user, RepositoryModel repo) {
+	
+		//Access Control
+		if (!user.canView(repo)) {
+			if (user == UserModel.ANONYMOUS) {
+				return Status.AuthenticationRequired;
+			} else {
+				return Status.Error_Unauthorized;
+			}
+		}
+
+		//Object Logic
+		if (!isValidOid(oid)) { 
+			return Status.Error_Invalid_Oid;
+		}
+		
+		if (!fileCache.containsKey(oid)) { 
+			return Status.Unavailable;
+		}
+		
+		FilestoreModel item = fileCache.get(oid);
+		
+		if (item.getStatus() == Status.Available) {
+			return Status.Available;
+		}
+		
+		return Status.Unavailable;
+	}
+	
+	@Override
+	public FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo) {
+		
+		if (canGetObject(oid, user, repo) == Status.Available) {
+			return fileCache.get(oid);
+		}
+		
+		return null;
+	}
+
+	@Override
+	public FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut) {
+		
+		//Access control and object logic
+		Status status = canGetObject(oid, user, repo);
+				
+		if (status != Status.Available) { 
+			return status;
+		}
+				
+		FilestoreModel item = fileCache.get(oid);
+		
+		if (streamOut != null) {
+			try (FileInputStream streamIn = new FileInputStream(getStoragePath(oid))) {
+				
+				IOUtils.copyLarge(streamIn, streamOut);
+				
+				streamOut.flush();
+				streamIn.close();
+			} catch (EofException e) {
+				logger.error(MessageFormat.format("Client aborted connection for {0}", oid), e);
+				return Status.Error_Unexpected_Stream_End;
+			} catch (Exception e) {
+				logger.error(MessageFormat.format("Failed to download blob {0}", oid), e);
+				return Status.Error_Unknown;
+			}
+		}
+		
+		return item.getStatus();
+	}
+
+	@Override
+	public List<FilestoreModel> getAllObjects() {
+		return new ArrayList<FilestoreModel>(fileCache.values());
+	}
+
+	@Override
+	public File getStorageFolder() {
+		return runtimeManager.getFileOrFolder(Keys.filestore.storageFolder, "${baseFolder}/lfs");
+	}
+	
+	@Override
+	public File getStoragePath(String oid) {
+		 return new File(getStorageFolder(), oid.substring(0, 2).concat("/").concat(oid.substring(2)));
+	}
+
+	@Override
+	public long getMaxUploadSize() {
+		return settings.getLong(Keys.filestore.maxUploadSize, -1);
+	}
+	
+	@Override
+	public long getFilestoreUsedByteCount() {
+		Iterator<FilestoreModel> iterator = fileCache.values().iterator();
+		long total = 0;
+		
+		while (iterator.hasNext()) {
+			
+			FilestoreModel item = iterator.next();
+			if (item.getStatus() == Status.Available) {
+				total += item.getSize();
+			}
+		}
+		
+		return total;
+	}
+	
+	@Override
+	public long getFilestoreAvailableByteCount() {
+		
+		try {
+			return Files.getFileStore(getStorageFolder().toPath()).getUsableSpace();
+		} catch (IOException e) {
+			logger.error(MessageFormat.format("Failed to retrive available space in Filestore {0}", e));
+		}
+		
+		return UNDEFINED_SIZE;
+	};
+	
+	private synchronized void saveFilestoreModel(FilestoreModel model) {
+		
+		File metaFile = new File(getStorageFolder(), METAFILE);
+		File metaFileTmp = new File(getStorageFolder(), METAFILE_TMP);
+		boolean isNewFile = false;
+		
+		try {
+			if (!metaFile.exists()) {
+				metaFile.getParentFile().mkdirs();
+				metaFile.createNewFile();
+				isNewFile = true;
+			}
+			FileUtils.copyFile(metaFile, metaFileTmp);
+			
+		} catch (IOException e) {
+			logger.error("Writing filestore model to file {0}, {1}", METAFILE, e);
+		}
+		
+		try (RandomAccessFile fs = new RandomAccessFile(metaFileTmp, "rw")) {
+		
+			if (isNewFile) {
+				fs.writeBytes("[");
+			} else {
+				fs.seek(fs.length() - 1);
+				fs.writeBytes(",");
+			}
+			
+			fs.writeBytes(gson().toJson(model));
+			fs.writeBytes("]");
+			
+			fs.close();
+			
+		} catch (IOException e) {
+			logger.error("Writing filestore model to file {0}, {1}", METAFILE_TMP, e);
+		}
+		
+		try {
+			if (metaFileTmp.exists()) {
+				FileUtils.copyFile(metaFileTmp, metaFile);
+				
+				metaFileTmp.delete();
+			} else {
+				logger.error("Writing filestore model to file {0}", METAFILE);
+			}
+		}
+		catch (IOException e) {
+			logger.error("Writing filestore model to file {0}, {1}", METAFILE, e);
+		}
+	}
+	
+	/*
+	 * Intended for testing purposes only
+	 */
+	public void clearFilestoreCache() {
+		fileCache.clear();
+	}
+	
+	private static Gson gson(ExclusionStrategy... strategies) {
+		GsonBuilder builder = new GsonBuilder();
+		builder.registerTypeAdapter(Date.class, new GmtDateTypeAdapter());
+		if (!ArrayUtils.isEmpty(strategies)) {
+			builder.setExclusionStrategies(strategies);
+		}
+		return builder.create();
+	}
+	
+}
diff --git a/src/main/java/com/gitblit/manager/GitblitManager.java b/src/main/java/com/gitblit/manager/GitblitManager.java
index 88fa804..4a385fc 100644
--- a/src/main/java/com/gitblit/manager/GitblitManager.java
+++ b/src/main/java/com/gitblit/manager/GitblitManager.java
@@ -21,6 +21,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.OutputStream;
 import java.lang.reflect.Type;
 import java.text.MessageFormat;
 import java.util.ArrayList;
@@ -49,15 +50,16 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Constants.AccessPermission;
-import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.FederationRequest;
 import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.Role;
 import com.gitblit.GitBlitException;
 import com.gitblit.IStoredSettings;
-import com.gitblit.Keys;
+import com.gitblit.extensions.RepositoryLifeCycleListener;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.FederationSet;
+import com.gitblit.models.FilestoreModel;
 import com.gitblit.models.ForkModel;
 import com.gitblit.models.GitClientApplication;
 import com.gitblit.models.Mailing;
@@ -68,7 +70,6 @@
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
 import com.gitblit.models.SearchResult;
 import com.gitblit.models.ServerSettings;
 import com.gitblit.models.ServerStatus;
@@ -79,7 +80,6 @@
 import com.gitblit.transport.ssh.IPublicKeyManager;
 import com.gitblit.transport.ssh.SshKey;
 import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.ObjectCache;
 import com.gitblit.utils.StringUtils;
@@ -88,6 +88,10 @@
 import com.google.gson.JsonIOException;
 import com.google.gson.JsonSyntaxException;
 import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 /**
  * GitblitManager is an aggregate interface delegate.  It implements all the manager
@@ -101,11 +105,16 @@
  * @author James Moger
  *
  */
+@Singleton
 public class GitblitManager implements IGitblit {
 
 	protected final Logger logger = LoggerFactory.getLogger(getClass());
 
 	protected final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
+
+	protected final Provider<IPublicKeyManager> publicKeyManagerProvider;
+
+	protected final Provider<ITicketService> ticketServiceProvider;
 
 	protected final IStoredSettings settings;
 
@@ -119,24 +128,30 @@
 
 	protected final IAuthenticationManager authenticationManager;
 
-	protected final IPublicKeyManager publicKeyManager;
-
 	protected final IRepositoryManager repositoryManager;
 
 	protected final IProjectManager projectManager;
 
 	protected final IFederationManager federationManager;
 
+	protected final IFilestoreManager filestoreManager;
+
+	@Inject
 	public GitblitManager(
+			Provider<IPublicKeyManager> publicKeyManagerProvider,
+			Provider<ITicketService> ticketServiceProvider,
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
 			INotificationManager notificationManager,
 			IUserManager userManager,
 			IAuthenticationManager authenticationManager,
-			IPublicKeyManager publicKeyManager,
 			IRepositoryManager repositoryManager,
 			IProjectManager projectManager,
-			IFederationManager federationManager) {
+			IFederationManager federationManager,
+			IFilestoreManager filestoreManager) {
+
+		this.publicKeyManagerProvider = publicKeyManagerProvider;
+		this.ticketServiceProvider = ticketServiceProvider;
 
 		this.settings = runtimeManager.getSettings();
 		this.runtimeManager = runtimeManager;
@@ -144,10 +159,10 @@
 		this.notificationManager = notificationManager;
 		this.userManager = userManager;
 		this.authenticationManager = authenticationManager;
-		this.publicKeyManager = publicKeyManager;
 		this.repositoryManager = repositoryManager;
 		this.projectManager = projectManager;
 		this.federationManager = federationManager;
+		this.filestoreManager = filestoreManager;
 	}
 
 	@Override
@@ -268,6 +283,16 @@
 
 		// add this clone to the cached model
 		repositoryManager.addToCachedRepositoryList(cloneModel);
+
+		if (pluginManager != null) {
+			for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) {
+				try {
+					listener.onFork(repository, cloneModel);
+				} catch (Throwable t) {
+					logger.error(String.format("failed to call plugin onFork %s", repository.name), t);
+				}
+			}
+		}
 		return cloneModel;
 	}
 
@@ -358,66 +383,6 @@
 	}
 
 	/**
-	 * Returns a list of repository URLs and the user access permission.
-	 *
-	 * @param request
-	 * @param user
-	 * @param repository
-	 * @return a list of repository urls
-	 */
-	@Override
-	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
-		if (user == null) {
-			user = UserModel.ANONYMOUS;
-		}
-		String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
-
-		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
-		// http/https url
-		if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
-			AccessPermission permission = user.getRepositoryPermission(repository).permission;
-			if (permission.exceeds(AccessPermission.NONE)) {
-				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
-			}
-		}
-
-		// add all other urls
-		// {0} = repository
-		// {1} = username
-		for (String url : settings.getStrings(Keys.web.otherUrls)) {
-			if (url.contains("{1}")) {
-				// external url requires username, only add url IF we have one
-				if (!StringUtils.isEmpty(username)) {
-					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
-				}
-			} else {
-				// external url does not require username
-				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
-			}
-		}
-		return list;
-	}
-
-	protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
-		String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
-		if (StringUtils.isEmpty(gitblitUrl)) {
-			gitblitUrl = HttpUtils.getGitblitURL(request);
-		}
-		StringBuilder sb = new StringBuilder();
-		sb.append(gitblitUrl);
-		sb.append(Constants.R_PATH);
-		sb.append(repository.name);
-
-		// inject username into repository url if authentication is required
-		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
-				&& !StringUtils.isEmpty(username)) {
-			sb.insert(sb.indexOf("://") + 3, username + "@");
-		}
-		return sb.toString();
-	}
-
-
-	/**
 	 * Returns the list of custom client applications to be used for the
 	 * repository url panel;
 	 *
@@ -492,7 +457,7 @@
 			// Read bundled Gitblit properties to extract setting descriptions.
 			// This copy is pristine and only used for populating the setting
 			// models map.
-			InputStream is = GitblitManager.class.getResourceAsStream("/reference.properties");
+			InputStream is = GitblitManager.class.getResourceAsStream("/defaults.properties");
 			BufferedReader propertiesReader = new BufferedReader(new InputStreamReader(is));
 			StringBuilder description = new StringBuilder();
 			SettingModel setting = new SettingModel();
@@ -537,24 +502,20 @@
 			}
 			propertiesReader.close();
 		} catch (NullPointerException e) {
-			logger.error("Failed to find resource copy of gitblit.properties");
+			logger.error("Failed to find classpath resource 'defaults.properties'");
 		} catch (IOException e) {
-			logger.error("Failed to load resource copy of gitblit.properties");
+			logger.error("Failed to load classpath resource 'defaults.properties'");
 		}
 	}
 
-	/**
-	 * Throws an exception if trying to get a ticket service.
-	 *
-	 */
 	@Override
 	public ITicketService getTicketService() {
-		throw new RuntimeException("This class does not have a ticket service!");
+		return ticketServiceProvider.get();
 	}
 
 	@Override
 	public IPublicKeyManager getPublicKeyManager() {
-		return publicKeyManager;
+		return publicKeyManagerProvider.get();
 	}
 
 	/*
@@ -605,26 +566,6 @@
 	}
 
 	@Override
-	public boolean isServingRepositories() {
-		return runtimeManager.isServingRepositories();
-	}
-
-	@Override
-	public boolean isServingHTTP() {
-		return runtimeManager.isServingHTTP();
-	}
-
-	@Override
-	public boolean isServingGIT() {
-		return runtimeManager.isServingGIT();
-	}
-
-	@Override
-	public boolean isServingSSH() {
-		return runtimeManager.isServingSSH();
-	}
-
-	@Override
 	public TimeZone getTimezone() {
 		return runtimeManager.getTimezone();
 	}
@@ -662,6 +603,11 @@
 	@Override
 	public ServerStatus getStatus() {
 		return runtimeManager.getStatus();
+	}
+
+	@Override
+	public Injector getInjector() {
+		return runtimeManager.getInjector();
 	}
 
 	@Override
@@ -703,8 +649,8 @@
 	 */
 
 	@Override
-	public UserModel authenticate(String username, char[] password) {
-		return authenticationManager.authenticate(username, password);
+	public UserModel authenticate(String username, char[] password, String remoteIP) {
+		return authenticationManager.authenticate(username, password, remoteIP);
 	}
 
 	@Override
@@ -719,6 +665,11 @@
 	@Override
 	public UserModel authenticate(String username, SshKey key) {
 		return authenticationManager.authenticate(username, key);
+	}
+
+	@Override
+	public UserModel authenticate(String username) {
+		return authenticationManager.authenticate(username);
 	}
 
 	@Override
@@ -782,6 +733,16 @@
 		return authenticationManager.supportsTeamMembershipChanges(team);
 	}
 
+	@Override
+	public boolean supportsRoleChanges(UserModel user, Role role) {
+		return authenticationManager.supportsRoleChanges(user, role);
+	}
+
+	@Override
+	public boolean supportsRoleChanges(TeamModel team, Role role) {
+		return authenticationManager.supportsRoleChanges(team, role);
+	}
+
 	/*
 	 * USER MANAGER
 	 */
@@ -803,11 +764,6 @@
 	@Override
 	public List<UserModel> getAllUsers() {
 		return userManager.getAllUsers();
-	}
-
-	@Override
-	public boolean deleteUser(String username) {
-		return userManager.deleteUser(username);
 	}
 
 	@Override
@@ -851,8 +807,22 @@
 	}
 
 	@Override
+	public boolean deleteUser(String username) {
+		// delegate to deleteUserModel() to delete public ssh keys
+		UserModel user = userManager.getUserModel(username);
+		return deleteUserModel(user);
+	}
+
+	/**
+	 * Delete the user and all associated public ssh keys.
+	 */
+	@Override
 	public boolean deleteUserModel(UserModel model) {
-		return userManager.deleteUserModel(model);
+		boolean success = userManager.deleteUserModel(model);
+		if (success) {
+			getPublicKeyManager().removeAllKeys(model.username);
+		}
+		return success;
 	}
 
 	@Override
@@ -1053,10 +1023,23 @@
 		return repositoryManager.getRepositoryDefaultMetrics(model, repository);
 	}
 
+	/**
+	 * Detect renames and reindex as appropriate.
+	 */
 	@Override
 	public void updateRepositoryModel(String repositoryName, RepositoryModel repository,
 			boolean isCreate) throws GitBlitException {
+		RepositoryModel oldModel = null;
+		boolean isRename = !isCreate && !repositoryName.equalsIgnoreCase(repository.name);
+		if (isRename) {
+			oldModel = repositoryManager.getRepositoryModel(repositoryName);
+		}
+
 		repositoryManager.updateRepositoryModel(repositoryName, repository, isCreate);
+
+		if (isRename && ticketServiceProvider.get() != null) {
+			ticketServiceProvider.get().rename(oldModel, repository);
+		}
 	}
 
 	@Override
@@ -1069,14 +1052,23 @@
 		return repositoryManager.canDelete(model);
 	}
 
+	/**
+	 * Delete the repository and all associated tickets.
+	 */
 	@Override
 	public boolean deleteRepositoryModel(RepositoryModel model) {
-		return repositoryManager.deleteRepositoryModel(model);
+		boolean success = repositoryManager.deleteRepositoryModel(model);
+		if (success && ticketServiceProvider.get() != null) {
+			ticketServiceProvider.get().deleteAll(model);
+		}
+		return success;
 	}
 
 	@Override
 	public boolean deleteRepository(String repositoryName) {
-		return repositoryManager.deleteRepository(repositoryName);
+		// delegate to deleteRepositoryModel() to destroy indexed tickets
+		RepositoryModel repository = repositoryManager.getRepositoryModel(repositoryName);
+		return deleteRepositoryModel(repository);
 	}
 
 	@Override
@@ -1253,6 +1245,70 @@
 	}
 
 	/*
+	 * FILE STORAGE MANAGER
+	 */
+	
+	@Override
+	public boolean isValidOid(String oid) {
+		return filestoreManager.isValidOid(oid);
+	}
+	
+	@Override
+	public FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo) {
+		return filestoreManager.addObject(oid, size, user, repo);
+	}
+	
+	@Override
+	public FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo) {
+		return filestoreManager.getObject(oid, user, repo);
+	};
+	
+	@Override
+	public FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn ) {
+		return filestoreManager.uploadBlob(oid, size, user, repo, streamIn);
+	}
+	
+	@Override
+	public FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut ) {
+		return filestoreManager.downloadBlob(oid, user, repo, streamOut);
+	}
+	
+	@Override
+	public List<FilestoreModel> getAllObjects() {
+		return filestoreManager.getAllObjects();
+	}
+	
+	@Override
+	public File getStorageFolder() {
+		return filestoreManager.getStorageFolder();
+	}
+	
+	@Override
+	public File getStoragePath(String oid) {
+		return filestoreManager.getStoragePath(oid);
+	}
+	
+	@Override
+	public long getMaxUploadSize() {
+		return filestoreManager.getMaxUploadSize();
+	};
+	
+	@Override
+	public void clearFilestoreCache() {
+		filestoreManager.clearFilestoreCache();
+	};
+
+	@Override
+	public long getFilestoreUsedByteCount() {
+		return filestoreManager.getFilestoreUsedByteCount();
+	};
+	
+	@Override
+	public long getFilestoreAvailableByteCount() {
+		return filestoreManager.getFilestoreAvailableByteCount();
+	};
+	
+	/*
 	 * PLUGIN MANAGER
 	 */
 
@@ -1355,4 +1411,5 @@
 	public PluginRelease lookupRelease(String pluginId, String version) {
 		return pluginManager.lookupRelease(pluginId, version);
 	}
+	
 }
diff --git a/src/main/java/com/gitblit/manager/IAuthenticationManager.java b/src/main/java/com/gitblit/manager/IAuthenticationManager.java
index 3600b32..5406a79 100644
--- a/src/main/java/com/gitblit/manager/IAuthenticationManager.java
+++ b/src/main/java/com/gitblit/manager/IAuthenticationManager.java
@@ -18,6 +18,7 @@
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.gitblit.Constants.Role;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.transport.ssh.SshKey;
@@ -64,10 +65,21 @@
 	 * @see IUserService.authenticate(String, char[])
 	 * @param username
 	 * @param password
+	 * @param remoteIP 
 	 * @return a user object or null
 	 * @since 1.4.0
 	 */
-	UserModel authenticate(String username, char[] password);
+	UserModel authenticate(String username, char[] password, String remoteIP);
+
+	/**
+	 * Return the UserModel for already authenticated user.
+	 *
+	 * @see IUserService.authenticate(String, char[])
+	 * @param username
+	 * @return a user object or null
+	 * @since 1.7.0
+	 */
+	UserModel authenticate(String username);
 
 	/**
 	 * Returns the Gitlbit cookie in the request.
@@ -161,4 +173,22 @@
 	 */
 	boolean supportsTeamMembershipChanges(TeamModel team);
 
+	/**
+	 * Returns true if the specified role can be changed.
+	 *
+	 * @param user
+	 * @return true if the specified role can be changed
+	 * @since 1.6.1
+	 */
+	boolean supportsRoleChanges(UserModel user, Role role);
+
+	/**
+	 * Returns true if the specified role can be changed.
+	 *
+	 * @param team
+	 * @return true if the specified role can be changed
+	 * @since 1.6.1
+	 */
+	boolean supportsRoleChanges(TeamModel team, Role role);
+
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/manager/IFilestoreManager.java b/src/main/java/com/gitblit/manager/IFilestoreManager.java
new file mode 100644
index 0000000..0720650
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/IFilestoreManager.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2015 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.manager;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+
+
+public interface IFilestoreManager extends IManager {
+
+	boolean isValidOid(String oid);
+	
+	FilestoreModel.Status addObject(String oid, long size, UserModel user, RepositoryModel repo);
+	
+	FilestoreModel getObject(String oid, UserModel user, RepositoryModel repo);
+	
+	FilestoreModel.Status uploadBlob(String oid, long size, UserModel user, RepositoryModel repo, InputStream streamIn );
+	
+	FilestoreModel.Status downloadBlob(String oid, UserModel user, RepositoryModel repo, OutputStream streamOut );
+	
+	List<FilestoreModel> getAllObjects();
+	
+	File getStorageFolder();
+	
+	File getStoragePath(String oid);
+	
+	long getMaxUploadSize();
+	
+	void clearFilestoreCache();
+	
+	long getFilestoreUsedByteCount();
+	
+	long getFilestoreAvailableByteCount();
+
+}
diff --git a/src/main/java/com/gitblit/manager/IGitblit.java b/src/main/java/com/gitblit/manager/IGitblit.java
index 50ec8b1..489de62 100644
--- a/src/main/java/com/gitblit/manager/IGitblit.java
+++ b/src/main/java/com/gitblit/manager/IGitblit.java
@@ -16,14 +16,10 @@
 package com.gitblit.manager;
 
 import java.util.Collection;
-import java.util.List;
-
-import javax.servlet.http.HttpServletRequest;
 
 import com.gitblit.GitBlitException;
 import com.gitblit.models.GitClientApplication;
 import com.gitblit.models.RepositoryModel;
-import com.gitblit.models.RepositoryUrl;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.tickets.ITicketService;
@@ -37,18 +33,8 @@
 									IAuthenticationManager,
 									IRepositoryManager,
 									IProjectManager,
-									IFederationManager {
-
-	/**
-	 * Returns a list of repository URLs and the user access permission.
-	 *
-	 * @param request
-	 * @param user
-	 * @param repository
-	 * @return a list of repository urls
-	 * @since 1.4.0
-	 */
-	List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository);
+									IFederationManager,
+									IFilestoreManager {
 
 	/**
 	 * Creates a complete user object.
diff --git a/src/main/java/com/gitblit/manager/IRuntimeManager.java b/src/main/java/com/gitblit/manager/IRuntimeManager.java
index 132534c..2203b7f 100644
--- a/src/main/java/com/gitblit/manager/IRuntimeManager.java
+++ b/src/main/java/com/gitblit/manager/IRuntimeManager.java
@@ -25,8 +25,11 @@
 import com.gitblit.models.ServerSettings;
 import com.gitblit.models.ServerStatus;
 import com.gitblit.utils.XssFilter;
+import com.google.inject.Injector;
 
 public interface IRuntimeManager extends IManager {
+
+	Injector getInjector();
 
 	void setBaseFolder(File folder);
 
@@ -47,42 +50,6 @@
  	 * @since 1.5.1
 	 */
 	Locale getLocale();
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * or if it is merely a repository viewer.
-	 *
-	 * @return true if Gitblit is serving repositories
- 	 * @since 1.4.0
-	 */
-	boolean isServingRepositories();
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * over HTTP.
-	 *
-	 * @return true if Gitblit is serving repositories over HTTP
- 	 * @since 1.6.0
-	 */
-	boolean isServingHTTP();
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * over the GIT Daemon protocol.
-	 *
-	 * @return true if Gitblit is serving repositories over the GIT Daemon protocol
- 	 * @since 1.6.0
-	 */
-	boolean isServingGIT();
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * over the SSH protocol.
-	 *
-	 * @return true if Gitblit is serving repositories over the SSH protocol
- 	 * @since 1.6.0
-	 */
-	boolean isServingSSH();
 
 	/**
 	 * Determine if this Gitblit instance is running in debug mode
diff --git a/src/main/java/com/gitblit/manager/IServicesManager.java b/src/main/java/com/gitblit/manager/IServicesManager.java
new file mode 100644
index 0000000..b3a973b
--- /dev/null
+++ b/src/main/java/com/gitblit/manager/IServicesManager.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2014 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.manager;
+
+import java.util.List;
+
+import javax.servlet.http.HttpServletRequest;
+
+import com.gitblit.Constants.Transport;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
+import com.gitblit.models.UserModel;
+
+public interface IServicesManager extends IManager {
+
+	/**
+	 * Determine if this Gitblit instance is actively serving git repositories
+	 * or if it is merely a repository viewer.
+	 *
+	 * @return true if Gitblit is serving repositories
+ 	 * @since 1.7.0
+	 */
+	boolean isServingRepositories();
+
+	/**
+	 * Determine if this Gitblit instance is actively serving git repositories
+	 * over HTTP.
+	 *
+	 * @return true if Gitblit is serving repositories over HTTP
+ 	 * @since 1.7.0
+	 */
+	boolean isServingHTTP();
+
+	/**
+	 * Determine if this Gitblit instance is actively serving git repositories
+	 * over HTTP.
+	 *
+	 * @return true if Gitblit is serving repositories over HTTPS
+ 	 * @since 1.7.0
+	 */
+	boolean isServingHTTPS();
+
+	/**
+	 * Determine if this Gitblit instance is actively serving git repositories
+	 * over the GIT Daemon protocol.
+	 *
+	 * @return true if Gitblit is serving repositories over the GIT Daemon protocol
+ 	 * @since 1.7.0
+	 */
+	boolean isServingGIT();
+
+	/**
+	 * Determine if this Gitblit instance is actively serving git repositories
+	 * over the SSH protocol.
+	 *
+	 * @return true if Gitblit is serving repositories over the SSH protocol
+ 	 * @since 1.7.0
+	 */
+	boolean isServingSSH();
+
+	/**
+	 * Returns a list of repository URLs and the user access permission.
+	 *
+	 * @param request
+	 * @param user
+	 * @param repository
+	 * @return a list of repository urls
+	 * @since 1.7.0
+	 */
+	List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository);
+
+	/**
+	 * Returns true if the transport may be used for pushing.
+	 *
+	 * @param byTransport
+	 * @return true if the transport can be used for pushes.
+	 * @since 1.7.0
+	 */
+	boolean acceptsPush(Transport byTransport);
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/manager/NotificationManager.java b/src/main/java/com/gitblit/manager/NotificationManager.java
index 69a611b..4bbc2ab 100644
--- a/src/main/java/com/gitblit/manager/NotificationManager.java
+++ b/src/main/java/com/gitblit/manager/NotificationManager.java
@@ -29,6 +29,8 @@
 import com.gitblit.Keys;
 import com.gitblit.models.Mailing;
 import com.gitblit.service.MailService;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * The notification manager dispatches notifications.  Currently, email is the
@@ -38,6 +40,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class NotificationManager implements INotificationManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -48,6 +51,7 @@
 
 	private final MailService mailService;
 
+	@Inject
 	public NotificationManager(IStoredSettings settings) {
 		this.settings = settings;
 		this.mailService = new MailService(settings);
diff --git a/src/main/java/com/gitblit/manager/PluginManager.java b/src/main/java/com/gitblit/manager/PluginManager.java
index 5e25caa..b3936e5 100644
--- a/src/main/java/com/gitblit/manager/PluginManager.java
+++ b/src/main/java/com/gitblit/manager/PluginManager.java
@@ -15,13 +15,15 @@
  */
 package com.gitblit.manager;
 
-import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.FileFilter;
 import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
 import java.net.Proxy;
 import java.net.URL;
 import java.net.URLConnection;
@@ -37,8 +39,12 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import ro.fortsoft.pf4j.DefaultPluginFactory;
 import ro.fortsoft.pf4j.DefaultPluginManager;
+import ro.fortsoft.pf4j.ExtensionFactory;
+import ro.fortsoft.pf4j.Plugin;
 import ro.fortsoft.pf4j.PluginClassLoader;
+import ro.fortsoft.pf4j.PluginFactory;
 import ro.fortsoft.pf4j.PluginState;
 import ro.fortsoft.pf4j.PluginStateEvent;
 import ro.fortsoft.pf4j.PluginStateListener;
@@ -56,8 +62,9 @@
 import com.gitblit.utils.FileUtils;
 import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.StringUtils;
-import com.google.common.io.Files;
-import com.google.common.io.InputSupplier;
+import com.google.common.io.ByteStreams;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * The plugin manager maintains the lifecycle of plugins. It is exposed as
@@ -68,32 +75,23 @@
  * @author James Moger
  *
  */
+@Singleton
 public class PluginManager implements IPluginManager, PluginStateListener {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
 
-	private final DefaultPluginManager pf4j;
-
 	private final IRuntimeManager runtimeManager;
+
+	private DefaultPluginManager pf4j;
 
 	// timeout defaults of Maven 3.0.4 in seconds
 	private int connectTimeout = 20;
 
 	private int readTimeout = 12800;
 
+	@Inject
 	public PluginManager(IRuntimeManager runtimeManager) {
-		File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
-		dir.mkdirs();
 		this.runtimeManager = runtimeManager;
-
-		this.pf4j = new DefaultPluginManager(dir);
-
-		try {
-			Version systemVersion = Version.createVersion(Constants.getVersion());
-			pf4j.setSystemVersion(systemVersion);
-		} catch (Exception e) {
-			logger.error(null, e);
-		}
 	}
 
 	@Override
@@ -108,6 +106,28 @@
 
 	@Override
 	public PluginManager start() {
+		File dir = runtimeManager.getFileOrFolder(Keys.plugins.folder, "${baseFolder}/plugins");
+		dir.mkdirs();
+
+		pf4j = new DefaultPluginManager(dir) {
+
+			@Override
+			protected PluginFactory createPluginFactory() {
+				return new GuicePluginFactory();
+			}
+
+			@Override
+			protected ExtensionFactory createExtensionFactory() {
+				return new GuiceExtensionFactory();
+			}
+		};
+
+		try {
+			Version systemVersion = Version.createVersion(Constants.getVersion());
+			pf4j.setSystemVersion(systemVersion);
+		} catch (Exception e) {
+			logger.error(null, e);
+		}
 		pf4j.loadPlugins();
 		logger.debug("Starting plugins");
 		pf4j.startPlugins();
@@ -438,7 +458,7 @@
 
 		}
 
-		if (sha1File == null && md5File == null && verifyChecksum) {
+		if (sha1File == null && md5File == null) {
 			throw new IOException("Missing SHA1 and MD5 checksums for " + url);
 		}
 
@@ -513,12 +533,9 @@
 		// try to get the server-specified last-modified date of this artifact
 		long lastModified = conn.getHeaderFieldDate("Last-Modified", System.currentTimeMillis());
 
-		Files.copy(new InputSupplier<InputStream>() {
-			@Override
-			public InputStream getInput() throws IOException {
-				return new BufferedInputStream(conn.getInputStream());
-			}
-		}, tmpFile);
+		try (InputStream is = conn.getInputStream(); OutputStream os = new FileOutputStream(tmpFile);) {
+			ByteStreams.copy(is, os);
+		}
 
 		File destFile = new File(pFolder, StringUtils.getLastPathElement(u.getPath()));
 		if (destFile.exists()) {
@@ -567,10 +584,55 @@
 	}
 
 	protected Proxy getProxy(URL url) {
-		return java.net.Proxy.NO_PROXY;
+		String proxyHost = runtimeManager.getSettings().getString(Keys.plugins.httpProxyHost, "");
+		String proxyPort = runtimeManager.getSettings().getString(Keys.plugins.httpProxyPort, "");
+
+		if (!StringUtils.isEmpty(proxyHost)  && !StringUtils.isEmpty(proxyPort)) {
+			return new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, Integer.parseInt(proxyPort)));
+		} else {
+			return java.net.Proxy.NO_PROXY;
+		}
 	}
 
 	protected String getProxyAuthorization(URL url) {
-		return "";
+		String proxyAuth = runtimeManager.getSettings().getString(Keys.plugins.httpProxyAuthorization, "");
+		return proxyAuth;
+	}
+
+	/**
+	 * Instantiates a plugin using pf4j but injects member fields
+	 * with Guice.
+	 */
+	private class GuicePluginFactory extends DefaultPluginFactory {
+
+		@Override
+		public Plugin create(PluginWrapper pluginWrapper) {
+			// use pf4j to create the plugin
+			Plugin plugin = super.create(pluginWrapper);
+
+			if (plugin != null) {
+				// allow Guice to inject member fields
+				runtimeManager.getInjector().injectMembers(plugin);
+			}
+
+			return plugin;
+		}
+	}
+
+	/**
+	 * Instantiates an extension using Guice.
+	 */
+	private class GuiceExtensionFactory implements ExtensionFactory {
+		@Override
+		public Object create(Class<?> extensionClass) {
+			// instantiate && inject the extension
+			logger.debug("Create instance for extension '{}'", extensionClass.getName());
+			try {
+				return runtimeManager.getInjector().getInstance(extensionClass);
+			} catch (Exception e) {
+				logger.error(e.getMessage(), e);
+			}
+			return null;
+		}
 	}
 }
diff --git a/src/main/java/com/gitblit/manager/ProjectManager.java b/src/main/java/com/gitblit/manager/ProjectManager.java
index 666f521..ae46bdf 100644
--- a/src/main/java/com/gitblit/manager/ProjectManager.java
+++ b/src/main/java/com/gitblit/manager/ProjectManager.java
@@ -41,6 +41,8 @@
 import com.gitblit.utils.ModelUtils;
 import com.gitblit.utils.ObjectCache;
 import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Project manager handles project-related functions.
@@ -48,6 +50,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class ProjectManager implements IProjectManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -68,6 +71,7 @@
 
 	private FileBasedConfig projectConfigs;
 
+	@Inject
 	public ProjectManager(
 			IRuntimeManager runtimeManager,
 			IUserManager userManager,
diff --git a/src/main/java/com/gitblit/manager/RepositoryManager.java b/src/main/java/com/gitblit/manager/RepositoryManager.java
index 2db4132..e2e4de6 100644
--- a/src/main/java/com/gitblit/manager/RepositoryManager.java
+++ b/src/main/java/com/gitblit/manager/RepositoryManager.java
@@ -91,6 +91,8 @@
 import com.gitblit.utils.ObjectCache;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Repository manager creates, updates, deletes and caches git repositories.  It
@@ -99,6 +101,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class RepositoryManager implements IRepositoryManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -121,7 +124,7 @@
 
 	private final IUserManager userManager;
 
-	private final File repositoriesFolder;
+	private File repositoriesFolder;
 
 	private LuceneService luceneExecutor;
 
@@ -129,6 +132,7 @@
 
 	private MirrorService mirrorExecutor;
 
+	@Inject
 	public RepositoryManager(
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
@@ -138,11 +142,11 @@
 		this.runtimeManager = runtimeManager;
 		this.pluginManager = pluginManager;
 		this.userManager = userManager;
-		this.repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
 	}
 
 	@Override
 	public RepositoryManager start() {
+		repositoriesFolder = runtimeManager.getFileOrFolder(Keys.git.repositoriesFolder, "${baseFolder}/git");
 		logger.info("Repositories folder : {}", repositoriesFolder.getAbsolutePath());
 
 		// initialize utilities
@@ -1109,9 +1113,16 @@
 			// find the root, cached
 			String key = getRepositoryKey(repository);
 			RepositoryModel model = repositoryListCache.get(key);
+			if (model == null) {
+				return null;
+			}
+
 			while (model.originRepository != null) {
 				String originKey = getRepositoryKey(model.originRepository);
 				model = repositoryListCache.get(originKey);
+				if (model == null) {
+					return null;
+				}
 			}
 			ForkModel root = getForkModelFromCache(model.name);
 			return root;
@@ -1343,7 +1354,7 @@
 	}
 
 	/**
-	 * Creates/updates the repository model keyed by reopsitoryName. Saves all
+	 * Creates/updates the repository model keyed by repositoryName. Saves all
 	 * repository settings in .git/config. This method allows for renaming
 	 * repositories and will update user access permissions accordingly.
 	 *
@@ -1371,6 +1382,7 @@
 				repository.name = repository.name.substring(projectPath.length() + 1);
 			}
 		}
+		boolean isRename = false;
 		if (isCreate) {
 			// ensure created repository name ends with .git
 			if (!repository.name.toLowerCase().endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
@@ -1387,7 +1399,8 @@
 			r = JGitUtils.createRepository(repositoriesFolder, repository.name, shared);
 		} else {
 			// rename repository
-			if (!repositoryName.equalsIgnoreCase(repository.name)) {
+			isRename = !repositoryName.equalsIgnoreCase(repository.name);
+			if (isRename) {
 				if (!repository.name.toLowerCase().endsWith(
 						org.eclipse.jgit.lib.Constants.DOT_GIT_EXT)) {
 					repository.name += org.eclipse.jgit.lib.Constants.DOT_GIT_EXT;
@@ -1505,6 +1518,14 @@
 					listener.onCreation(repository);
 				} catch (Throwable t) {
 					logger.error(String.format("failed to call plugin onCreation %s", repositoryName), t);
+				}
+			}
+		} else if (isRename && pluginManager != null) {
+			for (RepositoryLifeCycleListener listener : pluginManager.getExtensions(RepositoryLifeCycleListener.class)) {
+				try {
+					listener.onRename(repositoryName, repository);
+				} catch (Throwable t) {
+					logger.error(String.format("failed to call plugin onRename %s", repositoryName), t);
 				}
 			}
 		}
@@ -1967,21 +1988,19 @@
 	}
 
 	protected void confirmWriteAccess() {
-		if (runtimeManager.isServingRepositories()) {
-			try {
-				if (!getRepositoriesFolder().exists()) {
-					getRepositoriesFolder().mkdirs();
-				}
-				File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
-				file.delete();
-			} catch (Exception e) {
-				logger.error("");
-				logger.error(Constants.BORDER2);
-				logger.error("Please check filesystem permissions!");
-				logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
-				logger.error(Constants.BORDER2);
-				logger.error("");
+		try {
+			if (!getRepositoriesFolder().exists()) {
+				getRepositoriesFolder().mkdirs();
 			}
+			File file = File.createTempFile(".test-", ".txt", getRepositoriesFolder());
+			file.delete();
+		} catch (Exception e) {
+			logger.error("");
+			logger.error(Constants.BORDER2);
+			logger.error("Please check filesystem permissions!");
+			logger.error("FAILED TO WRITE TO REPOSITORIES FOLDER!!", e);
+			logger.error(Constants.BORDER2);
+			logger.error("");
 		}
 	}
 }
diff --git a/src/main/java/com/gitblit/manager/RuntimeManager.java b/src/main/java/com/gitblit/manager/RuntimeManager.java
index 219bf80..18d6b9c 100644
--- a/src/main/java/com/gitblit/manager/RuntimeManager.java
+++ b/src/main/java/com/gitblit/manager/RuntimeManager.java
@@ -33,7 +33,11 @@
 import com.gitblit.models.SettingModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.XssFilter;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
 
+@Singleton
 public class RuntimeManager implements IRuntimeManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -50,6 +54,10 @@
 
 	private TimeZone timezone;
 
+	@Inject
+	private Injector injector;
+
+	@Inject
 	public RuntimeManager(IStoredSettings settings, XssFilter xssFilter) {
 		this(settings, xssFilter, null);
 	}
@@ -76,6 +84,11 @@
 	@Override
 	public RuntimeManager stop() {
 		return this;
+	}
+
+	@Override
+	public Injector getInjector() {
+		return injector;
 	}
 
 	@Override
@@ -113,52 +126,6 @@
 		}
 //		settingsModel.pushScripts = getAllScripts();
 		return settingsModel;
-	}
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * or if it is merely a repository viewer.
-	 *
-	 * @return true if Gitblit is serving repositories
-	 */
-	@Override
-	public boolean isServingRepositories() {
-		return isServingHTTP()
-				|| isServingGIT()
-				|| isServingSSH();
-	}
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * over the HTTP protocol.
-	 *
-	 * @return true if Gitblit is serving repositories over the HTTP protocol
-	 */
-	@Override
-	public boolean isServingHTTP() {
-		return settings.getBoolean(Keys.git.enableGitServlet, true);
-	}
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * over the Git Daemon protocol.
-	 *
-	 * @return true if Gitblit is serving repositories over the Git Daemon protocol
-	 */
-	@Override
-	public boolean isServingGIT() {
-		return settings.getInteger(Keys.git.daemonPort, 0) > 0;
-	}
-
-	/**
-	 * Determine if this Gitblit instance is actively serving git repositories
-	 * over the SSH protocol.
-	 *
-	 * @return true if Gitblit is serving repositories over the SSH protocol
-	 */
-	@Override
-	public boolean isServingSSH() {
-		return settings.getInteger(Keys.git.sshPort, 0) > 0;
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/manager/ServicesManager.java b/src/main/java/com/gitblit/manager/ServicesManager.java
index 437fd10..b993eb6 100644
--- a/src/main/java/com/gitblit/manager/ServicesManager.java
+++ b/src/main/java/com/gitblit/manager/ServicesManager.java
@@ -18,9 +18,15 @@
 import java.io.IOException;
 import java.net.URI;
 import java.text.MessageFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
@@ -30,9 +36,11 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.Constants;
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.FederationToken;
+import com.gitblit.Constants.Transport;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.fanout.FanoutNioService;
@@ -40,14 +48,18 @@
 import com.gitblit.fanout.FanoutSocketService;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
 import com.gitblit.models.UserModel;
 import com.gitblit.service.FederationPullService;
 import com.gitblit.transport.git.GitDaemon;
 import com.gitblit.transport.ssh.SshDaemon;
-import com.gitblit.utils.IdGenerator;
+import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
 import com.gitblit.utils.WorkQueue;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
 /**
  * Services manager manages long-running services/processes that either have no
@@ -57,19 +69,18 @@
  * @author James Moger
  *
  */
-public class ServicesManager implements IManager {
+@Singleton
+public class ServicesManager implements IServicesManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
 
 	private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
 
+	private final Provider<WorkQueue> workQueueProvider;
+
 	private final IStoredSettings settings;
 
 	private final IGitblit gitblit;
-
-	private final IdGenerator idGenerator;
-
-	private final WorkQueue workQueue;
 
 	private FanoutService fanoutService;
 
@@ -77,12 +88,16 @@
 
 	private SshDaemon sshDaemon;
 
-	public ServicesManager(IGitblit gitblit) {
-		this.settings = gitblit.getSettings();
+	@Inject
+	public ServicesManager(
+			Provider<WorkQueue> workQueueProvider,
+			IStoredSettings settings,
+			IGitblit gitblit) {
+
+		this.workQueueProvider = workQueueProvider;
+
+		this.settings = settings;
 		this.gitblit = gitblit;
-		int defaultThreadPoolSize = settings.getInteger(Keys.execution.defaultThreadPoolSize, 1);
-		this.idGenerator = new IdGenerator();
-		this.workQueue = new WorkQueue(idGenerator, defaultThreadPoolSize);
 	}
 
 	@Override
@@ -107,24 +122,217 @@
 		if (sshDaemon != null) {
 			sshDaemon.stop();
 		}
-		workQueue.stop();
+		workQueueProvider.get().stop();
 		return this;
 	}
 
+	protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
+		String gitblitUrl = settings.getString(Keys.web.canonicalUrl, null);
+		if (StringUtils.isEmpty(gitblitUrl)) {
+			gitblitUrl = HttpUtils.getGitblitURL(request);
+		}
+		StringBuilder sb = new StringBuilder();
+		sb.append(gitblitUrl);
+		sb.append(Constants.R_PATH);
+		sb.append(repository.name);
+
+		// inject username into repository url if authentication is required
+		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
+				&& !StringUtils.isEmpty(username)) {
+			sb.insert(sb.indexOf("://") + 3, username + "@");
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * Returns a list of repository URLs and the user access permission.
+	 *
+	 * @param request
+	 * @param user
+	 * @param repository
+	 * @return a list of repository urls
+	 */
+	@Override
+	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
+		}
+		String username = StringUtils.encodeUsername(UserModel.ANONYMOUS.equals(user) ? "" : user.username);
+
+		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
+
+		// http/https url
+		if (settings.getBoolean(Keys.git.enableGitServlet, true) &&
+			settings.getBoolean(Keys.web.showHttpServletUrls, true)) {
+			AccessPermission permission = user.getRepositoryPermission(repository).permission;
+			if (permission.exceeds(AccessPermission.NONE)) {
+				String repoUrl = getRepositoryUrl(request, username, repository);
+				Transport transport = Transport.fromUrl(repoUrl);
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
+				list.add(new RepositoryUrl(repoUrl, permission));
+			}
+		}
+
+		// ssh daemon url
+		String sshDaemonUrl = getSshDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(sshDaemonUrl) &&
+			settings.getBoolean(Keys.web.showSshDaemonUrls, true)) {
+			AccessPermission permission = user.getRepositoryPermission(repository).permission;
+			if (permission.exceeds(AccessPermission.NONE)) {
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(Transport.SSH)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
+
+				list.add(new RepositoryUrl(sshDaemonUrl, permission));
+			}
+		}
+
+		// git daemon url
+		String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(gitDaemonUrl) &&
+				settings.getBoolean(Keys.web.showGitDaemonUrls, true)) {
+			AccessPermission permission = getGitDaemonAccessPermission(user, repository);
+			if (permission.exceeds(AccessPermission.NONE)) {
+				if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(Transport.GIT)) {
+					// downgrade the repo permission for this transport
+					// because it is not an acceptable PUSH transport
+					permission = AccessPermission.CLONE;
+				}
+				list.add(new RepositoryUrl(gitDaemonUrl, permission));
+			}
+		}
+
+		// add all other urls
+		// {0} = repository
+		// {1} = username
+		boolean advertisePermsForOther = settings.getBoolean(Keys.web.advertiseAccessPermissionForOtherUrls, false);
+		for (String url : settings.getStrings(Keys.web.otherUrls)) {
+			String externalUrl = null;
+
+			if (url.contains("{1}")) {
+				// external url requires username, only add url IF we have one
+				if (StringUtils.isEmpty(username)) {
+					continue;
+				} else {
+					externalUrl = MessageFormat.format(url, repository.name, username);
+				}
+			} else {
+				// external url does not require username, just do repo name formatting
+				externalUrl = MessageFormat.format(url, repository.name);
+			}
+
+			AccessPermission permission = null;
+			if (advertisePermsForOther) {
+				permission = user.getRepositoryPermission(repository).permission;
+				if (permission.exceeds(AccessPermission.NONE)) {
+					Transport transport = Transport.fromUrl(externalUrl);
+					if (permission.atLeast(AccessPermission.PUSH) && !acceptsPush(transport)) {
+						// downgrade the repo permission for this transport
+						// because it is not an acceptable PUSH transport
+						permission = AccessPermission.CLONE;
+					}
+				}
+			}
+			list.add(new RepositoryUrl(externalUrl, permission));
+		}
+
+		// sort transports by highest permission and then by transport security
+		Collections.sort(list, new Comparator<RepositoryUrl>() {
+
+			@Override
+			public int compare(RepositoryUrl o1, RepositoryUrl o2) {
+				if (o1.hasPermission() && !o2.hasPermission()) {
+					// prefer known permission items over unknown
+					return -1;
+				} else if (!o1.hasPermission() && o2.hasPermission()) {
+					// prefer known permission items over unknown
+					return 1;
+				} else if (!o1.hasPermission() && !o2.hasPermission()) {
+					// sort by Transport ordinal
+					return o1.transport.compareTo(o2.transport);
+				} else if (o1.permission.exceeds(o2.permission)) {
+					// prefer highest permission
+					return -1;
+				} else if (o2.permission.exceeds(o1.permission)) {
+					// prefer highest permission
+					return 1;
+				}
+
+				// prefer more secure transports
+				return o1.transport.compareTo(o2.transport);
+			}
+		});
+
+		// consider the user's transport preference
+		RepositoryUrl preferredUrl = null;
+		Transport preferredTransport = user.getPreferences().getTransport();
+		if (preferredTransport != null) {
+			Iterator<RepositoryUrl> itr = list.iterator();
+			while (itr.hasNext()) {
+				RepositoryUrl url = itr.next();
+				if (url.transport.equals(preferredTransport)) {
+					itr.remove();
+					preferredUrl = url;
+					break;
+				}
+			}
+		}
+		if (preferredUrl != null) {
+			list.add(0, preferredUrl);
+		}
+
+		return list;
+	}
+
+	/* (non-Javadoc)
+	 * @see com.gitblit.manager.IServicesManager#isServingRepositories()
+	 */
+	@Override
 	public boolean isServingRepositories() {
-		return isServingHTTP()
+		return isServingHTTPS()
+				|| isServingHTTP()
 				|| isServingGIT()
 				|| isServingSSH();
 	}
 
+	/* (non-Javadoc)
+	 * @see com.gitblit.manager.IServicesManager#isServingHTTP()
+	 */
+	@Override
 	public boolean isServingHTTP() {
-		return settings.getBoolean(Keys.git.enableGitServlet, true);
+		return settings.getBoolean(Keys.git.enableGitServlet, true)
+				&& ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpPort, 0) > 0)
+						|| !gitblit.getStatus().isGO);
 	}
 
+	/* (non-Javadoc)
+	 * @see com.gitblit.manager.IServicesManager#isServingHTTPS()
+	 */
+	@Override
+	public boolean isServingHTTPS() {
+		return settings.getBoolean(Keys.git.enableGitServlet, true)
+				&& ((gitblit.getStatus().isGO && settings.getInteger(Keys.server.httpsPort, 0) > 0)
+						|| !gitblit.getStatus().isGO);
+	}
+
+	/* (non-Javadoc)
+	 * @see com.gitblit.manager.IServicesManager#isServingGIT()
+	 */
+	@Override
 	public boolean isServingGIT() {
 		return gitDaemon != null && gitDaemon.isRunning();
 	}
 
+	/* (non-Javadoc)
+	 * @see com.gitblit.manager.IServicesManager#isServingSSH()
+	 */
+	@Override
 	public boolean isServingSSH() {
 		return sshDaemon != null && sshDaemon.isRunning();
 	}
@@ -158,6 +366,33 @@
 		}
 	}
 
+	@Override
+	public boolean acceptsPush(Transport byTransport) {
+		if (byTransport == null) {
+			logger.info("Unknown transport, push rejected!");
+			return false;
+		}
+
+		Set<Transport> transports = new HashSet<Transport>();
+		for (String value : settings.getStrings(Keys.git.acceptedPushTransports)) {
+			Transport transport = Transport.fromString(value);
+			if (transport == null) {
+				logger.info(String.format("Ignoring unknown registered transport %s", value));
+				continue;
+			}
+
+			transports.add(transport);
+		}
+
+		if (transports.isEmpty()) {
+			// no transports are explicitly specified, all are acceptable
+			return true;
+		}
+
+		// verify that the transport is permitted
+		return transports.contains(byTransport);
+	}
+
 	protected void configureGitDaemon() {
 		int port = settings.getInteger(Keys.git.daemonPort, 0);
 		String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
@@ -179,7 +414,7 @@
 		String bindInterface = settings.getString(Keys.git.sshBindInterface, "localhost");
 		if (port > 0) {
 			try {
-				sshDaemon = new SshDaemon(gitblit, workQueue);
+				sshDaemon = new SshDaemon(gitblit, workQueueProvider.get());
 				sshDaemon.start();
 			} catch (IOException e) {
 				sshDaemon = null;
@@ -285,7 +520,7 @@
 	 */
 	protected String getHostname(HttpServletRequest request) {
 		String hostname = request.getServerName();
-		String canonicalUrl = gitblit.getSettings().getString(Keys.web.canonicalUrl, null);
+		String canonicalUrl = settings.getString(Keys.web.canonicalUrl, null);
 		if (!StringUtils.isEmpty(canonicalUrl)) {
 			try {
 				URI uri = new URI(canonicalUrl);
diff --git a/src/main/java/com/gitblit/manager/UserManager.java b/src/main/java/com/gitblit/manager/UserManager.java
index 2b82ffb..e88ac93 100644
--- a/src/main/java/com/gitblit/manager/UserManager.java
+++ b/src/main/java/com/gitblit/manager/UserManager.java
@@ -36,6 +36,8 @@
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * The user manager manages persistence and retrieval of users and teams.
@@ -43,6 +45,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class UserManager implements IUserManager {
 
 	private final Logger logger = LoggerFactory.getLogger(getClass());
@@ -57,6 +60,7 @@
 
 	private IUserService userService;
 
+	@Inject
 	public UserManager(IRuntimeManager runtimeManager, IPluginManager pluginManager) {
 		this.settings = runtimeManager.getSettings();
 		this.runtimeManager = runtimeManager;
@@ -79,9 +83,9 @@
 	 * @param userService
 	 */
 	public void setUserService(IUserService userService) {
-		logger.info(userService.toString());
 		this.userService = userService;
 		this.userService.setup(runtimeManager);
+		logger.info(userService.toString());
 	}
 
 	@Override
@@ -111,10 +115,12 @@
 					// check to see if this "file" is a custom user service class
 					Class<?> realmClass = Class.forName(realm);
 					service = (IUserService) realmClass.newInstance();
-				} catch (Throwable t) {
+				} catch (ClassNotFoundException t) {
 					// typical file path configuration
 					File realmFile = runtimeManager.getFileOrFolder(Keys.realm.userService, "${baseFolder}/users.conf");
 					service = createUserService(realmFile);
+				} catch (InstantiationException | IllegalAccessException  e) {
+					logger.error("failed to instantiate user service {}: {}", realm, e.getMessage());
 				}
 			}
 			setUserService(service);
diff --git a/src/main/java/com/gitblit/models/FilestoreModel.java b/src/main/java/com/gitblit/models/FilestoreModel.java
new file mode 100644
index 0000000..ff7b210
--- /dev/null
+++ b/src/main/java/com/gitblit/models/FilestoreModel.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2015 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.models;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+/**
+ * A FilestoreModel represents a file stored outside a repository but referenced by the repository using a unique objectID
+ * 
+ * @author Paul Martin
+ *
+ */
+public class FilestoreModel implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public final String oid;
+	
+	private Long size;
+	private Status status;
+	
+	//Audit
+	private String stateChangedBy;
+	private Date stateChangedOn;
+
+	//Access Control
+	private List<String> repositories;
+	
+	public FilestoreModel(String id, long expectedSize, UserModel user, String repo) {
+		oid = id;
+		size = expectedSize;
+		status = Status.Upload_Pending;
+		stateChangedBy = user.getName();
+		stateChangedOn = new Date();
+		repositories = new ArrayList<String>();
+		repositories.add(repo);
+	}
+	
+	public synchronized long getSize() {
+		return size;
+	}
+	
+	public synchronized Status getStatus() {
+		return status;
+	}
+	
+	public synchronized String getChangedBy() {
+		return stateChangedBy;
+	}
+	
+	public synchronized Date getChangedOn() {
+		return stateChangedOn;
+	}
+	
+	public synchronized void setStatus(Status status, UserModel user) {
+		this.status = status;
+		stateChangedBy = user.getName();
+		stateChangedOn = new Date();
+	}
+	
+	public synchronized void reset(UserModel user, long size) {
+		status = Status.Upload_Pending;
+		stateChangedBy = user.getName();
+		stateChangedOn = new Date();
+		this.size = size;
+	}
+	
+	/*
+	 *  Handles possible race condition with concurrent connections
+	 *  @return true if action can proceed, false otherwise
+	 */
+	public synchronized boolean actionUpload(UserModel user) {
+		if (status == Status.Upload_Pending) {
+			status = Status.Upload_In_Progress;
+			stateChangedBy = user.getName();
+			stateChangedOn = new Date();
+			return true;
+		}
+		
+		return false;
+	}
+	
+	public synchronized boolean isInErrorState() {
+		return (this.status.value < 0);
+	}
+	
+	public synchronized void addRepository(String repo) {
+		if (!repositories.contains(repo)) {
+			repositories.add(repo);
+		}	
+	}
+	
+	public synchronized void removeRepository(String repo) {
+		repositories.remove(repo);
+	}
+	
+	public static enum Status {
+
+		Deleted(-30),
+		AuthenticationRequired(-20),
+		
+		Error_Unknown(-8),
+		Error_Unexpected_Stream_End(-7),
+		Error_Invalid_Oid(-6),
+		Error_Invalid_Size(-5),
+		Error_Hash_Mismatch(-4),
+		Error_Size_Mismatch(-3), 
+		Error_Exceeds_Size_Limit(-2),
+		Error_Unauthorized(-1),
+		//Negative values provide additional information and may be treated as 0 when not required
+		Unavailable(0),
+		Upload_Pending(1),
+		Upload_In_Progress(2),
+		Available(3);
+
+		final int value;
+
+		Status(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+
+		@Override
+		public String toString() {
+			return name().toLowerCase().replace('_', ' ');
+		}
+
+		public static Status fromState(int state) {
+			for (Status s : values()) {
+				if (s.getValue() == state) {
+					return s;
+				}
+			}
+			throw new NoSuchElementException(String.valueOf(state));
+		}
+	}
+
+}
+
diff --git a/src/main/java/com/gitblit/models/RefModel.java b/src/main/java/com/gitblit/models/RefModel.java
index 02ba130..d20c2dc 100644
--- a/src/main/java/com/gitblit/models/RefModel.java
+++ b/src/main/java/com/gitblit/models/RefModel.java
@@ -58,12 +58,7 @@
 				}
 			} else if (referencedObject instanceof RevCommit) {
 				RevCommit commit = (RevCommit) referencedObject;
-				PersonIdent committer = commit.getCommitterIdent();
-				if (committer != null) {
-					date = committer.getWhen();
-				} else {
-					date = JGitUtils.getCommitDate(commit);
-				}
+				date = JGitUtils.getAuthorDate(commit);
 			}
 		}
 		return date;
diff --git a/src/main/java/com/gitblit/models/RepositoryUrl.java b/src/main/java/com/gitblit/models/RepositoryUrl.java
index d155dbd..13f6917 100644
--- a/src/main/java/com/gitblit/models/RepositoryUrl.java
+++ b/src/main/java/com/gitblit/models/RepositoryUrl.java
@@ -41,8 +41,8 @@
 		this.permission = permission;
 	}
 
-	public boolean isExternal() {
-		return permission == null;
+	public boolean hasPermission() {
+		return permission != null;
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/models/TicketModel.java b/src/main/java/com/gitblit/models/TicketModel.java
index a4880ea..fd0b09e 100644
--- a/src/main/java/com/gitblit/models/TicketModel.java
+++ b/src/main/java/com/gitblit/models/TicketModel.java
@@ -91,6 +91,10 @@
 
 	public Integer deletions;
 
+	public Priority priority;
+	
+	public Severity severity;
+	
 	/**
 	 * Builds an effective ticket from the collection of changes.  A change may
 	 * Add or Subtract information from a ticket, but the collection of changes
@@ -141,6 +145,8 @@
 		changes = new ArrayList<Change>();
 		status = Status.New;
 		type = Type.defaultType;
+		priority = Priority.defaultPriority;
+		severity = Severity.defaultSeverity;
 	}
 
 	public boolean isOpen() {
@@ -516,6 +522,12 @@
 					break;
 				case mergeSha:
 					mergeSha = toString(value);
+					break;
+				case priority:
+					priority = TicketModel.Priority.fromObject(value, priority);
+					break;
+				case severity:
+					severity = TicketModel.Severity.fromObject(value, severity);
 					break;
 				default:
 					// unknown
@@ -1183,16 +1195,16 @@
 
 	public static enum Field {
 		title, body, responsible, type, status, milestone, mergeSha, mergeTo,
-		topic, labels, watchers, reviewers, voters, mentions;
+		topic, labels, watchers, reviewers, voters, mentions, priority, severity;
 	}
 
 	public static enum Type {
-		Enhancement, Task, Bug, Proposal, Question;
+		Enhancement, Task, Bug, Proposal, Question, Maintenance;
 
 		public static Type defaultType = Task;
 
 		public static Type [] choices() {
-			return new Type [] { Enhancement, Task, Bug, Question };
+			return new Type [] { Enhancement, Task, Bug, Question, Maintenance };
 		}
 
 		@Override
@@ -1310,4 +1322,110 @@
 			return null;
 		}
 	}
+
+	public static enum Priority {
+		Low(-1), Normal(0), High(1), Urgent(2);
+
+		public static Priority defaultPriority = Normal;
+
+		final int value;
+
+		Priority(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+		
+		public static Priority [] choices() {
+			return new Priority [] { Urgent, High, Normal, Low };
+		}
+
+		@Override
+		public String toString() {
+			return name().toLowerCase().replace('_', ' ');
+		}
+
+		public static Priority fromObject(Object o, Priority defaultPriority) {
+			if (o instanceof Priority) {
+				// cast and return
+				return (Priority) o;
+			} else if (o instanceof String) {
+				// find by name
+				for (Priority priority : values()) {
+					String str = o.toString();
+					if (priority.name().equalsIgnoreCase(str)
+							|| priority.toString().equalsIgnoreCase(str)) {
+						return priority;
+					}
+				}
+			} else if (o instanceof Number) {
+
+				switch (((Number) o).intValue()) {
+					case -1: return Priority.Low;
+					case 0:  return Priority.Normal;
+					case 1:  return Priority.High;
+					case 2:  return Priority.Urgent;
+					default: return Priority.Normal;
+				}
+			}
+
+			return defaultPriority;
+		}
+	}
+	
+	public static enum Severity {
+		Unrated(-1), Negligible(1), Minor(2), Serious(3), Critical(4), Catastrophic(5);
+
+		public static Severity defaultSeverity = Unrated;
+		
+		final int value;
+		
+		Severity(int value) {
+			this.value = value;
+		}
+
+		public int getValue() {
+			return value;
+		}
+		
+		public static Severity [] choices() {
+			return new Severity [] { Unrated, Negligible, Minor, Serious, Critical, Catastrophic };
+		}
+
+		@Override
+		public String toString() {
+			return name().toLowerCase().replace('_', ' ');
+		}
+		
+		public static Severity fromObject(Object o, Severity defaultSeverity) {
+			if (o instanceof Severity) {
+				// cast and return
+				return (Severity) o;
+			} else if (o instanceof String) {
+				// find by name
+				for (Severity severity : values()) {
+					String str = o.toString();
+					if (severity.name().equalsIgnoreCase(str)
+							|| severity.toString().equalsIgnoreCase(str)) {
+						return severity;
+					}
+				}
+			} else if (o instanceof Number) {
+				
+				switch (((Number) o).intValue()) {
+					case -1: return Severity.Unrated;
+					case 1:  return Severity.Negligible;
+					case 2:  return Severity.Minor;
+					case 3:  return Severity.Serious;
+					case 4:  return Severity.Critical;
+					case 5:  return Severity.Catastrophic;
+					default: return Severity.Unrated;
+				}
+			}
+
+			return defaultSeverity;
+		}
+	}
 }
diff --git a/src/main/java/com/gitblit/service/LuceneService.java b/src/main/java/com/gitblit/service/LuceneService.java
index 482be5c..097a39b 100644
--- a/src/main/java/com/gitblit/service/LuceneService.java
+++ b/src/main/java/com/gitblit/service/LuceneService.java
@@ -105,7 +105,7 @@
 public class LuceneService implements Runnable {
 
 
-	private static final int INDEX_VERSION = 5;
+	private static final int INDEX_VERSION = 6;
 
 	private static final String FIELD_OBJECT_TYPE = "type";
 	private static final String FIELD_PATH = "path";
@@ -125,7 +125,7 @@
 	private static final String CONF_ALIAS = "aliases";
 	private static final String CONF_BRANCH = "branches";
 
-	private static final Version LUCENE_VERSION = Version.LUCENE_46;
+	private static final Version LUCENE_VERSION = Version.LUCENE_4_10_0;
 
 	private final Logger logger = LoggerFactory.getLogger(LuceneService.class);
 
@@ -437,7 +437,7 @@
 					// skip non-annotated tags
 					continue;
 				}
-				if (!tags.containsKey(tag.getObjectId().getName())) {
+				if (!tags.containsKey(tag.getReferencedObjectId().getName())) {
 					tags.put(tag.getReferencedObjectId().getName(), new ArrayList<String>());
 				}
 				tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName);
@@ -615,7 +615,7 @@
 			}
 
 			// finished
-			reader.release();
+			reader.close();
 
 			// commit all changes and reset the searcher
 			config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION);
@@ -1104,6 +1104,7 @@
 			content = "";
 		}
 
+		int tabLength = storedSettings.getInteger(Keys.web.tabLength, 4);
 		int fragmentLength = SearchObjectType.commit == result.type ? 512 : 150;
 
 		QueryScorer scorer = new QueryScorer(query, "content");
@@ -1126,7 +1127,7 @@
 			if (fragment.length() > fragmentLength) {
 				fragment = fragment.substring(0, fragmentLength) + "...";
 			}
-			return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true) + "</pre>";
+			return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true, tabLength) + "</pre>";
 		}
 
 		// make sure we have unique fragments
diff --git a/src/main/java/com/gitblit/service/MailService.java b/src/main/java/com/gitblit/service/MailService.java
index ae9727f..ec3a84c 100644
--- a/src/main/java/com/gitblit/service/MailService.java
+++ b/src/main/java/com/gitblit/service/MailService.java
@@ -37,6 +37,7 @@
 import javax.mail.internet.MimeBodyPart;
 import javax.mail.internet.MimeMessage;
 import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimeUtility;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -196,7 +197,8 @@
 			}
 
 			message.setSentDate(new Date());
-			message.setSubject(mailing.subject);
+			// UTF-8 encode
+			message.setSubject(MimeUtility.encodeText(mailing.subject, "utf-8", "B"));
 
 			MimeBodyPart messagePart = new MimeBodyPart();
 			messagePart.setText(mailing.content, "utf-8");
diff --git a/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java b/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java
new file mode 100644
index 0000000..ae0797e
--- /dev/null
+++ b/src/main/java/com/gitblit/servlet/AccessDeniedServlet.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2015 Jean-Baptiste Mayer
+ *
+ * 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.servlet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.inject.Singleton;
+
+/**
+ * Access-denied Servlet.
+ * 
+ * This servlet serves only 404 Not Found error replies.
+ * 
+ * This servlet is used to override the container's default behavior to serve
+ * all contents of the application's WAR. We can selectively prevent access to
+ * a specific path simply by mapping this servlet onto specific paths.
+ * 
+ * 
+ * @author Jean-Baptiste Mayer
+ *
+ */
+@Singleton
+public class AccessDeniedServlet extends HttpServlet {
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = -3239463647917811122L;
+
+	@Override
+	protected void doPost(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, java.io.IOException {
+		processRequest(request, response);
+	}
+
+	@Override
+	protected void doGet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+		processRequest(request, response);
+	}
+
+	private void processRequest(HttpServletRequest request,
+			HttpServletResponse response) {
+		response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+	}
+}
diff --git a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
index 7f69119..bfbc089 100644
--- a/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
+++ b/src/main/java/com/gitblit/servlet/AccessRestrictionFilter.java
@@ -17,22 +17,22 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.Collections;
+import java.util.Iterator;
 
 import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
 
 /**
  * The AccessRestrictionFilter is an AuthenticationFilter that confirms that the
@@ -54,11 +54,15 @@
 
 	protected IRepositoryManager repositoryManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
-		super.inject(dagger, filterConfig);
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
+	protected AccessRestrictionFilter(
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager) {
+
+		super(authenticationManager);
+
+		this.runtimeManager = runtimeManager;
+		this.repositoryManager = repositoryManager;
 	}
 
 	/**
@@ -82,16 +86,17 @@
 	 *
 	 * @return true if the filter allows repository creation
 	 */
-	protected abstract boolean isCreationAllowed();
+	protected abstract boolean isCreationAllowed(String action);
 
 	/**
 	 * Determine if the action may be executed on the repository.
 	 *
 	 * @param repository
 	 * @param action
+	 * @param method
 	 * @return true if the action may be performed
 	 */
-	protected abstract boolean isActionAllowed(RepositoryModel repository, String action);
+	protected abstract boolean isActionAllowed(RepositoryModel repository, String action, String method);
 
 	/**
 	 * Determine if the repository requires authentication.
@@ -100,7 +105,7 @@
 	 * @param action
 	 * @return true if authentication required
 	 */
-	protected abstract boolean requiresAuthentication(RepositoryModel repository, String action);
+	protected abstract boolean requiresAuthentication(RepositoryModel repository, String action, String method);
 
 	/**
 	 * Determine if the user can access the repository and perform the specified
@@ -124,7 +129,26 @@
 	protected RepositoryModel createRepository(UserModel user, String repository, String action) {
 		return null;
 	}
-
+	
+	/**
+	 * Allows authentication header to be altered based on the action requested
+	 * Default is WWW-Authenticate
+	 * @param action
+	 * @return authentication type header
+	 */
+	protected String getAuthenticationHeader(String action) {
+		return "WWW-Authenticate";
+	}
+	
+	/**
+	 * Allows request headers to be used as part of filtering
+	 * @param request
+	 * @return true (default) if headers are valid, false otherwise
+	 */
+	protected boolean hasValidRequestHeader(String action, HttpServletRequest request) {
+		return true;
+	}
+	
 	/**
 	 * doFilter does the actual work of preprocessing the request to ensure that
 	 * the user may proceed.
@@ -161,13 +185,14 @@
 		// Load the repository model
 		RepositoryModel model = repositoryManager.getRepositoryModel(repository);
 		if (model == null) {
-			if (isCreationAllowed()) {
+			if (isCreationAllowed(urlRequestType)) {
 				if (user == null) {
 					// challenge client to provide credentials for creation. send 401.
 					if (runtimeManager.isDebugMode()) {
 						logger.info(MessageFormat.format("ARF: CREATE CHALLENGE {0}", fullUrl));
 					}
-					httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+					
+					httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE);
 					httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
 					return;
 				} else {
@@ -186,7 +211,7 @@
 		}
 
 		// Confirm that the action may be executed on the repository
-		if (!isActionAllowed(model, urlRequestType)) {
+		if (!isActionAllowed(model, urlRequestType, httpRequest.getMethod())) {
 			logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
 					urlRequestType, model, HttpServletResponse.SC_FORBIDDEN));
 			httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
@@ -208,13 +233,13 @@
 		}
 
 		// BASIC authentication challenge and response processing
-		if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType)) {
+		if (!StringUtils.isEmpty(urlRequestType) && requiresAuthentication(model, urlRequestType,  httpRequest.getMethod())) {
 			if (user == null) {
 				// challenge client to provide credentials. send 401.
 				if (runtimeManager.isDebugMode()) {
 					logger.info(MessageFormat.format("ARF: CHALLENGE {0}", fullUrl));
 				}
-				httpResponse.setHeader("WWW-Authenticate", CHALLENGE);
+				httpResponse.setHeader(getAuthenticationHeader(urlRequestType), CHALLENGE);
 				httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
 				return;
 			} else {
@@ -246,4 +271,17 @@
 		// pass processing to the restricted servlet.
 		chain.doFilter(authenticatedRequest, httpResponse);
 	}
+	
+	public static boolean hasContentInRequestHeader(HttpServletRequest request, String headerName, String content)
+	{
+		Iterator<String> headerItr = Collections.list(request.getHeaders(headerName)).iterator();
+		
+		while (headerItr.hasNext()) {
+			if (headerItr.next().contains(content)) {
+				return true;
+			}
+		}
+
+		return false;
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java
index c21f869..3093e63 100644
--- a/src/main/java/com/gitblit/servlet/AuthenticationFilter.java
+++ b/src/main/java/com/gitblit/servlet/AuthenticationFilter.java
@@ -1,184 +1,190 @@
-/*
- * Copyright 2011 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.servlet;
-
-import java.io.IOException;
-import java.security.Principal;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.gitblit.Constants;
-import com.gitblit.dagger.DaggerFilter;
-import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.models.UserModel;
-import com.gitblit.utils.DeepCopier;
-import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
-
-/**
- * The AuthenticationFilter is a servlet filter that preprocesses requests that
- * match its url pattern definition in the web.xml file.
- *
- * http://en.wikipedia.org/wiki/Basic_access_authentication
- *
- * @author James Moger
- *
- */
-public abstract class AuthenticationFilter extends DaggerFilter {
-
-	protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
-
-	protected static final String SESSION_SECURED = "com.gitblit.secured";
-
-	protected transient Logger logger = LoggerFactory.getLogger(getClass());
-
-	protected IAuthenticationManager authenticationManager;
-
-	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
-		this.authenticationManager = dagger.get(IAuthenticationManager.class);
-	}
-
-	/**
-	 * doFilter does the actual work of preprocessing the request to ensure that
-	 * the user may proceed.
-	 *
-	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
-	 *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
-	 */
-	@Override
-	public abstract void doFilter(final ServletRequest request, final ServletResponse response,
-			final FilterChain chain) throws IOException, ServletException;
-
-	/**
-	 * Allow the filter to require a client certificate to continue processing.
-	 *
-	 * @return true, if a client certificate is required
-	 */
-	protected boolean requiresClientCertificate() {
-		return false;
-	}
-
-	/**
-	 * Returns the full relative url of the request.
-	 *
-	 * @param httpRequest
-	 * @return url
-	 */
-	protected String getFullUrl(HttpServletRequest httpRequest) {
-		String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
-		String url = httpRequest.getRequestURI().substring(servletUrl.length());
-		String params = httpRequest.getQueryString();
-		if (url.length() > 0 && url.charAt(0) == '/') {
-			url = url.substring(1);
-		}
-		String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
-		return fullUrl;
-	}
-
-	/**
-	 * Returns the user making the request, if the user has authenticated.
-	 *
-	 * @param httpRequest
-	 * @return user
-	 */
-	protected UserModel getUser(HttpServletRequest httpRequest) {
-		UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
-		return user;
-	}
-
-	/**
-	 * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
-	 */
-	protected void newSession(HttpServletRequest request, HttpServletResponse response) {
-		HttpSession oldSession = request.getSession(false);
-		if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
-			synchronized (this) {
-				Map<String, Object> attributes = new HashMap<String, Object>();
-				Enumeration<String> e = oldSession.getAttributeNames();
-				while (e.hasMoreElements()) {
-					String name = e.nextElement();
-					attributes.put(name, oldSession.getAttribute(name));
-					oldSession.removeAttribute(name);
-				}
-				oldSession.invalidate();
-
-				HttpSession newSession = request.getSession(true);
-				newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
-				for (Map.Entry<String, Object> entry : attributes.entrySet()) {
-					newSession.setAttribute(entry.getKey(), entry.getValue());
-				}
-			}
-		}
-	}
-
-	/**
-	 * Wraps a standard HttpServletRequest and overrides user principal methods.
-	 */
-	public static class AuthenticatedRequest extends HttpServletRequestWrapper {
-
-		private UserModel user;
-
-		public AuthenticatedRequest(HttpServletRequest req) {
-			super(req);
-			user = DeepCopier.copy(UserModel.ANONYMOUS);
-		}
-
-		UserModel getUser() {
-			return user;
-		}
-
-		void setUser(UserModel user) {
-			this.user = user;
-		}
-
-		@Override
-		public String getRemoteUser() {
-			return user.username;
-		}
-
-		@Override
-		public boolean isUserInRole(String role) {
-			if (role.equals(Constants.ADMIN_ROLE)) {
-				return user.canAdmin();
-			}
-			// Gitblit does not currently use actual roles in the traditional
-			// servlet container sense.  That is the reason this is marked
-			// deprecated, but I may want to revisit this.
-			return user.hasRepositoryPermission(role);
-		}
-
-		@Override
-		public Principal getUserPrincipal() {
-			return user;
-		}
-	}
-}
+/*
+ * Copyright 2011 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.servlet;
+
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.Role;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.DeepCopier;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The AuthenticationFilter is a servlet filter that preprocesses requests that
+ * match its url pattern definition in the web.xml file.
+ *
+ * http://en.wikipedia.org/wiki/Basic_access_authentication
+ *
+ * @author James Moger
+ *
+ */
+public abstract class AuthenticationFilter implements Filter {
+
+	protected static final String CHALLENGE = "Basic realm=\"" + Constants.NAME + "\"";
+
+	protected static final String SESSION_SECURED = "com.gitblit.secured";
+
+	protected transient Logger logger = LoggerFactory.getLogger(getClass());
+
+	protected IAuthenticationManager authenticationManager;
+
+	protected AuthenticationFilter(IAuthenticationManager authenticationManager) {
+		this.authenticationManager = authenticationManager;
+	}
+
+	@Override
+	public void init(FilterConfig filterConfig) throws ServletException {
+	}
+
+	@Override
+	public void destroy() {
+	}
+
+	/**
+	 * doFilter does the actual work of preprocessing the request to ensure that
+	 * the user may proceed.
+	 *
+	 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
+	 *      javax.servlet.ServletResponse, javax.servlet.FilterChain)
+	 */
+	@Override
+	public abstract void doFilter(final ServletRequest request, final ServletResponse response,
+			final FilterChain chain) throws IOException, ServletException;
+
+	/**
+	 * Allow the filter to require a client certificate to continue processing.
+	 *
+	 * @return true, if a client certificate is required
+	 */
+	protected boolean requiresClientCertificate() {
+		return false;
+	}
+
+	/**
+	 * Returns the full relative url of the request.
+	 *
+	 * @param httpRequest
+	 * @return url
+	 */
+	protected String getFullUrl(HttpServletRequest httpRequest) {
+		String servletUrl = httpRequest.getContextPath() + httpRequest.getServletPath();
+		String url = httpRequest.getRequestURI().substring(servletUrl.length());
+		String params = httpRequest.getQueryString();
+		if (url.length() > 0 && url.charAt(0) == '/') {
+			url = url.substring(1);
+		}
+		String fullUrl = url + (StringUtils.isEmpty(params) ? "" : ("?" + params));
+		return fullUrl;
+	}
+
+	/**
+	 * Returns the user making the request, if the user has authenticated.
+	 *
+	 * @param httpRequest
+	 * @return user
+	 */
+	protected UserModel getUser(HttpServletRequest httpRequest) {
+		UserModel user = authenticationManager.authenticate(httpRequest, requiresClientCertificate());
+		return user;
+	}
+
+	/**
+	 * Taken from Jetty's LoginAuthenticator.renewSessionOnAuthentication()
+	 */
+	protected void newSession(HttpServletRequest request, HttpServletResponse response) {
+		HttpSession oldSession = request.getSession(false);
+		if (oldSession != null && oldSession.getAttribute(SESSION_SECURED) == null) {
+			synchronized (this) {
+				Map<String, Object> attributes = new HashMap<String, Object>();
+				Enumeration<String> e = oldSession.getAttributeNames();
+				while (e.hasMoreElements()) {
+					String name = e.nextElement();
+					attributes.put(name, oldSession.getAttribute(name));
+					oldSession.removeAttribute(name);
+				}
+				oldSession.invalidate();
+
+				HttpSession newSession = request.getSession(true);
+				newSession.setAttribute(SESSION_SECURED, Boolean.TRUE);
+				for (Map.Entry<String, Object> entry : attributes.entrySet()) {
+					newSession.setAttribute(entry.getKey(), entry.getValue());
+				}
+			}
+		}
+	}
+
+	/**
+	 * Wraps a standard HttpServletRequest and overrides user principal methods.
+	 */
+	public static class AuthenticatedRequest extends HttpServletRequestWrapper {
+
+		private UserModel user;
+
+		public AuthenticatedRequest(HttpServletRequest req) {
+			super(req);
+			user = DeepCopier.copy(UserModel.ANONYMOUS);
+		}
+
+		UserModel getUser() {
+			return user;
+		}
+
+		void setUser(UserModel user) {
+			this.user = user;
+		}
+
+		@Override
+		public String getRemoteUser() {
+			return user.username;
+		}
+
+		@Override
+		public boolean isUserInRole(String role) {
+			if (role.equals(Role.ADMIN.getRole())) {
+				return user.canAdmin();
+			}
+			// Gitblit does not currently use actual roles in the traditional
+			// servlet container sense.  That is the reason this is marked
+			// deprecated, but I may want to revisit this.
+			return user.hasRepositoryPermission(role);
+		}
+
+		@Override
+		public Principal getUserPrincipal() {
+			return user;
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java
index fa2152c..85fbb74 100644
--- a/src/main/java/com/gitblit/servlet/BranchGraphServlet.java
+++ b/src/main/java/com/gitblit/servlet/BranchGraphServlet.java
@@ -36,7 +36,10 @@
 import java.util.TreeSet;
 
 import javax.imageio.ImageIO;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -55,12 +58,9 @@
 import com.gitblit.Constants;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
 
 /**
  * Handles requests for branch graphs
@@ -68,7 +68,8 @@
  * @author James Moger
  *
  */
-public class BranchGraphServlet extends DaggerServlet {
+@Singleton
+public class BranchGraphServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
@@ -87,18 +88,18 @@
 
 	private IRepositoryManager repositoryManager;
 
-	public BranchGraphServlet() {
-		super();
+	@Inject
+	public BranchGraphServlet(
+			IStoredSettings settings,
+			IRepositoryManager repositoryManager) {
+
+		this.settings = settings;
+		this.repositoryManager = repositoryManager;
+
 		strokeCache = new Stroke[4];
 		for (int i = 1; i < strokeCache.length; i++) {
 			strokeCache[i] = new BasicStroke(i);
 		}
-	}
-
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java
index 42257a2..146f6d4 100644
--- a/src/main/java/com/gitblit/servlet/DownloadZipFilter.java
+++ b/src/main/java/com/gitblit/servlet/DownloadZipFilter.java
@@ -15,7 +15,13 @@
  */
 package com.gitblit.servlet;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 
@@ -27,7 +33,17 @@
  * @author James Moger
  *
  */
+@Singleton
 public class DownloadZipFilter extends AccessRestrictionFilter {
+
+	@Inject
+	public DownloadZipFilter(
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager) {
+
+		super(runtimeManager, authenticationManager, repositoryManager);
+	}
 
 	/**
 	 * Extract the repository name from the url.
@@ -65,7 +81,7 @@
 	 * @return true if the filter allows repository creation
 	 */
 	@Override
-	protected boolean isCreationAllowed() {
+	protected boolean isCreationAllowed(String action) {
 		return false;
 	}
 
@@ -74,10 +90,11 @@
 	 *
 	 * @param repository
 	 * @param action
+	 * @param method
 	 * @return true if the action may be performed
 	 */
 	@Override
-	protected boolean isActionAllowed(RepositoryModel repository, String action) {
+	protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
 		return true;
 	}
 
@@ -86,10 +103,11 @@
 	 *
 	 * @param repository
 	 * @param action
+	 * @param method
 	 * @return true if authentication required
 	 */
 	@Override
-	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+	protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
 		return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
 	}
 
diff --git a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java
index 6a64778..0756256 100644
--- a/src/main/java/com/gitblit/servlet/DownloadZipServlet.java
+++ b/src/main/java/com/gitblit/servlet/DownloadZipServlet.java
@@ -20,7 +20,10 @@
 import java.text.ParseException;
 import java.util.Date;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletResponse;
 
 import org.eclipse.jgit.lib.Repository;
@@ -31,14 +34,11 @@
 import com.gitblit.Constants;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.utils.CompressionUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
 
 /**
  * Streams out a zip file from the specified repository for any tree path at any
@@ -47,7 +47,8 @@
  * @author James Moger
  *
  */
-public class DownloadZipServlet extends DaggerServlet {
+@Singleton
+public class DownloadZipServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
@@ -76,10 +77,10 @@
 		}
 	}
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
+	@Inject
+	public DownloadZipServlet(IStoredSettings settings, IRepositoryManager repositoryManager) {
+		this.settings = settings;
+		this.repositoryManager = repositoryManager;
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
index c015021..8a3f782 100644
--- a/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
+++ b/src/main/java/com/gitblit/servlet/EnforceAuthenticationFilter.java
@@ -18,6 +18,9 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -31,11 +34,8 @@
 
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerFilter;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.models.UserModel;
-
-import dagger.ObjectGraph;
 
 /**
  * This filter enforces authentication via HTTP Basic Authentication, if the settings indicate so.
@@ -45,7 +45,8 @@
  * @author Laurens Vrijnsen
  *
  */
-public class EnforceAuthenticationFilter extends DaggerFilter {
+@Singleton
+public class EnforceAuthenticationFilter implements Filter {
 
 	protected transient Logger logger = LoggerFactory.getLogger(getClass());
 
@@ -53,10 +54,21 @@
 
 	private IAuthenticationManager authenticationManager;
 
+	@Inject
+	public EnforceAuthenticationFilter(
+			IStoredSettings settings,
+			IAuthenticationManager authenticationManager) {
+
+		this.settings = settings;
+		this.authenticationManager = authenticationManager;
+	}
+
 	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.authenticationManager = dagger.get(IAuthenticationManager.class);
+	public void init(FilterConfig config) {
+	}
+
+	@Override
+	public void destroy() {
 	}
 
 	/*
@@ -86,13 +98,5 @@
 			// user is authenticated, or don't care, continue handling
 			chain.doFilter(request, response);
 		}
-	}
-
-
-	/*
-	 * @see javax.servlet.Filter#destroy()
-	 */
-	@Override
-	public void destroy() {
 	}
 }
diff --git a/src/main/java/com/gitblit/servlet/FederationServlet.java b/src/main/java/com/gitblit/servlet/FederationServlet.java
index acbc002..78709c9 100644
--- a/src/main/java/com/gitblit/servlet/FederationServlet.java
+++ b/src/main/java/com/gitblit/servlet/FederationServlet.java
@@ -25,6 +25,8 @@
 import java.util.Map;
 import java.util.Set;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.http.HttpServletResponse;
 
 import com.gitblit.Constants.FederationRequest;
@@ -43,14 +45,13 @@
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
 
-import dagger.ObjectGraph;
-
 /**
  * Handles federation requests.
  *
  * @author James Moger
  *
  */
+@Singleton
 public class FederationServlet extends JsonServlet {
 
 	private static final long serialVersionUID = 1L;
@@ -63,12 +64,17 @@
 
 	private IFederationManager federationManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.userManager = dagger.get(IUserManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
-		this.federationManager = dagger.get(IFederationManager.class);
+	@Inject
+	public FederationServlet(
+			IStoredSettings settings,
+			IUserManager userManager,
+			IRepositoryManager repositoryManager,
+			IFederationManager federationManager) {
+
+		this.settings = settings;
+		this.userManager = userManager;
+		this.repositoryManager = repositoryManager;
+		this.federationManager = federationManager;
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/servlet/FilestoreServlet.java b/src/main/java/com/gitblit/servlet/FilestoreServlet.java
new file mode 100644
index 0000000..1975148
--- /dev/null
+++ b/src/main/java/com/gitblit/servlet/FilestoreServlet.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright 2015 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.servlet;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.Constants;
+import com.gitblit.IStoredSettings;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.manager.IGitblit;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.JsonUtils;
+
+
+/**
+ * Handles large file storage as per the Git LFS v1 Batch API
+ * 
+ * Further details can be found at https://github.com/github/git-lfs
+ * 
+ * @author Paul Martin
+ */
+@Singleton
+public class FilestoreServlet extends HttpServlet {
+
+	private static final long serialVersionUID = 1L;
+	public static final int PROTOCOL_VERSION = 1;
+	
+	public static final String GIT_LFS_META_MIME = "application/vnd.git-lfs+json";
+	
+	public static final String REGEX_PATH = "^(.*?)/(r|git)/(.*?)/info/lfs/objects/(batch|" + Constants.REGEX_SHA256 + ")";
+	public static final int REGEX_GROUP_BASE_URI = 1;
+	public static final int REGEX_GROUP_PREFIX = 2;
+	public static final int REGEX_GROUP_REPOSITORY = 3;
+	public static final int REGEX_GROUP_ENDPOINT = 4;
+	
+	protected final Logger logger;
+	
+	private static IGitblit gitblit;
+
+	@Inject
+	public FilestoreServlet(IStoredSettings settings, IGitblit gitblit) {
+		
+		super();
+		logger = LoggerFactory.getLogger(getClass());
+		
+		FilestoreServlet.gitblit = gitblit;
+	}
+
+		
+	/**
+	 * Handles batch upload request (metadata)
+	 *
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	@Override
+	protected void doPost(HttpServletRequest request, 
+			HttpServletResponse response) throws ServletException ,IOException {
+		
+		UrlInfo info = getInfoFromRequest(request);
+		if (info == null) {
+			sendError(response, HttpServletResponse.SC_NOT_FOUND);
+        	return;
+		}
+
+		//Post is for batch operations so no oid should be defined
+		if (info.oid != null) {
+			sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+			return;
+		}
+		
+		IGitLFS.Batch batch = deserialize(request, response, IGitLFS.Batch.class);
+		
+		if (batch == null) { 
+			sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+			return;
+		}
+
+		UserModel user = getUserOrAnonymous(request);
+		
+		IGitLFS.BatchResponse batchResponse = new IGitLFS.BatchResponse();
+		
+		if (batch.operation.equalsIgnoreCase("upload")) {
+			for (IGitLFS.Request item : batch.objects) {
+				
+				Status state = gitblit.addObject(item.oid, item.size, user, info.repository);
+
+				batchResponse.objects.add(getResponseForUpload(info.baseUrl, item.oid, item.size, user.getName(), info.repository.name, state));
+			}
+		} else if (batch.operation.equalsIgnoreCase("download")) {
+			for (IGitLFS.Request item : batch.objects) {
+				
+				Status state = gitblit.downloadBlob(item.oid, user, info.repository, null);
+				batchResponse.objects.add(getResponseForDownload(info.baseUrl, item.oid, item.size, user.getName(), info.repository.name, state));
+			}
+		} else {
+			sendError(response, HttpServletResponse.SC_NOT_IMPLEMENTED);
+			return;
+		}
+		
+		response.setStatus(HttpServletResponse.SC_OK);
+		serialize(response, batchResponse);
+	}
+	
+	/**
+	 * Handles the actual upload (BLOB)
+	 * 
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	@Override
+	protected void doPut(HttpServletRequest request, 
+			HttpServletResponse response) throws ServletException ,IOException {
+		
+		UrlInfo info = getInfoFromRequest(request);
+		
+		if (info == null) {
+			sendError(response, HttpServletResponse.SC_NOT_FOUND);
+        	return;
+		}
+
+		//Put is a singular operation so must have oid
+		if (info.oid == null) {
+			sendError(response, HttpServletResponse.SC_BAD_REQUEST);
+			return;
+		}
+		
+		UserModel user = getUserOrAnonymous(request);
+		long size = FilestoreManager.UNDEFINED_SIZE;
+		
+		
+		
+		FilestoreModel.Status status = gitblit.uploadBlob(info.oid, size, user, info.repository, request.getInputStream());
+		IGitLFS.Response responseObject = getResponseForUpload(info.baseUrl, info.oid, size, user.getName(), info.repository.name, status);
+		
+		logger.info(MessageFormat.format("FILESTORE-AUDIT {0}:{4} {1} {2}@{3}", 
+				"PUT", info.oid, user.getName(), info.repository.name, status.toString() ));
+		
+		if (responseObject.error == null) {
+			response.setStatus(responseObject.successCode);
+		} else {
+			serialize(response, responseObject.error);
+		}
+	};
+	
+	/**
+	 * Handles a download
+	 * Treated as hypermedia request if accept header contains Git-LFS MIME
+	 * otherwise treated as a download of the blob
+	 * @param request
+	 * @param response
+	 * @throws javax.servlet.ServletException
+	 * @throws java.io.IOException
+	 */
+	@Override
+	protected void doGet(HttpServletRequest request, 
+			HttpServletResponse response) throws ServletException ,IOException {
+		
+		UrlInfo info = getInfoFromRequest(request);
+		
+		if (info == null || info.oid == null) {
+			sendError(response, HttpServletResponse.SC_NOT_FOUND);
+        	return;
+		}
+		
+		UserModel user = getUserOrAnonymous(request);
+		
+		FilestoreModel model = gitblit.getObject(info.oid, user, info.repository);
+		long size = FilestoreManager.UNDEFINED_SIZE;
+		
+		boolean isMetaRequest = AccessRestrictionFilter.hasContentInRequestHeader(request, "Accept", GIT_LFS_META_MIME);
+		FilestoreModel.Status status = Status.Unavailable;
+		
+		if (model != null) {
+			size = model.getSize();
+			status = model.getStatus();
+		}
+		
+		if (!isMetaRequest) {
+			status = gitblit.downloadBlob(info.oid, user, info.repository, response.getOutputStream());
+			
+			logger.info(MessageFormat.format("FILESTORE-AUDIT {0}:{4} {1} {2}@{3}", 
+					"GET", info.oid, user.getName(), info.repository.name, status.toString() ));
+		}
+		
+		if (status == Status.Error_Unexpected_Stream_End) {
+			return;
+		}
+
+		IGitLFS.Response responseObject = getResponseForDownload(info.baseUrl, 
+				info.oid, size, user.getName(), info.repository.name, status);
+		
+		if (responseObject.error == null) {
+			response.setStatus(responseObject.successCode);
+			
+			if (isMetaRequest) {
+				serialize(response, responseObject);
+			}
+		} else {
+			response.setStatus(responseObject.error.code);
+			serialize(response, responseObject.error);
+		}
+	};
+	
+	private void sendError(HttpServletResponse response, int code) throws IOException {
+		
+		String msg = "";
+		
+		switch (code)
+		{
+			case HttpServletResponse.SC_NOT_FOUND: msg = "Not Found"; break;
+			case HttpServletResponse.SC_NOT_IMPLEMENTED: msg = "Not Implemented"; break;
+			case HttpServletResponse.SC_BAD_REQUEST: msg = "Malformed Git-LFS request"; break;
+			
+			default: msg = "Unknown Error";
+		}
+		
+		response.setStatus(code);
+		serialize(response, new IGitLFS.ObjectError(code, msg));
+	}
+	
+	@SuppressWarnings("incomplete-switch")
+	private IGitLFS.Response getResponseForUpload(String baseUrl, String oid, long size, String user, String repo, FilestoreModel.Status state) {
+
+		switch (state) {
+			case AuthenticationRequired:
+				return new IGitLFS.Response(oid, size, 401, MessageFormat.format("Authentication required to write to repository {0}", repo));
+			case Error_Unauthorized: 
+				return new IGitLFS.Response(oid, size, 403, MessageFormat.format("User {0}, does not have write permissions to repository {1}", user, repo));
+			case Error_Exceeds_Size_Limit: 
+				return new IGitLFS.Response(oid, size, 509, MessageFormat.format("Object is larger than allowed limit of {1}",  gitblit.getMaxUploadSize()));
+			case Error_Hash_Mismatch: 
+				return new IGitLFS.Response(oid, size, 422, "Hash mismatch");
+			case Error_Invalid_Oid: 
+				return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid oid", oid));
+			case Error_Invalid_Size: 
+				return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid size", size));
+			case Error_Size_Mismatch: 
+				return new IGitLFS.Response(oid, size, 422, "Object size mismatch");
+			case Deleted: 
+				return new IGitLFS.Response(oid, size, 410, "Object was deleted : ".concat("TBD Reason") );
+			case Upload_In_Progress:
+				return new IGitLFS.Response(oid, size, 503, "File currently being uploaded by another user");
+			case Unavailable: 
+				return new IGitLFS.Response(oid, size, 404, MessageFormat.format("Repository {0}, does not exist for user {1}", repo, user));
+			case Upload_Pending: 
+				return new IGitLFS.Response(oid, size, 202, "upload", getObjectUri(baseUrl, repo, oid) );
+			case Available: 
+				return new IGitLFS.Response(oid, size, 200, "upload", getObjectUri(baseUrl, repo, oid) );
+		}
+		
+		return new IGitLFS.Response(oid, size, 500, "Unknown Error");
+	}
+
+	@SuppressWarnings("incomplete-switch")
+	private IGitLFS.Response getResponseForDownload(String baseUrl, String oid, long size, String user, String repo, FilestoreModel.Status state) {
+
+		switch (state) {
+			case Error_Unauthorized: 
+				return new IGitLFS.Response(oid, size, 403, MessageFormat.format("User {0}, does not have read permissions to repository {1}", user, repo));
+			case Error_Invalid_Oid: 
+				return new IGitLFS.Response(oid, size, 422, MessageFormat.format("{0} is not a valid oid", oid));
+			case Error_Unknown:
+				return new IGitLFS.Response(oid, size, 500, "Unknown Error");
+			case Deleted: 
+				return new IGitLFS.Response(oid, size, 410, "Object was deleted : ".concat("TBD Reason") );
+			case Available: 
+				return new IGitLFS.Response(oid, size, 200, "download", getObjectUri(baseUrl, repo, oid) );
+		}
+		
+		return new IGitLFS.Response(oid, size, 404, "Object not available");
+	}
+
+	
+	private String getObjectUri(String baseUrl, String repo, String oid) {
+		return baseUrl + "/" + repo + "/" + Constants.R_LFS + "objects/" + oid;
+	}
+	
+	
+	protected void serialize(HttpServletResponse response, Object o) throws IOException {
+		if (o != null) {
+			// Send JSON response
+			String json = JsonUtils.toJsonString(o);
+			response.setCharacterEncoding(Constants.ENCODING);
+			response.setContentType(GIT_LFS_META_MIME);
+			response.getWriter().append(json);
+		}
+	}
+	
+	protected <X> X deserialize(HttpServletRequest request, HttpServletResponse response,
+			Class<X> clazz) {
+		
+		String json = "";
+		try {
+			
+			json = readJson(request, response);
+			
+			return JsonUtils.fromJsonString(json.toString(), clazz);
+			
+		} catch (Exception e) {
+			//Intentional silent fail
+		}
+		
+		return null;
+	}
+	
+	private String readJson(HttpServletRequest request, HttpServletResponse response)
+			throws IOException {
+		BufferedReader reader = request.getReader();
+		StringBuilder json = new StringBuilder();
+		String line = null;
+		while ((line = reader.readLine()) != null) {
+			json.append(line);
+		}
+		reader.close();
+
+		if (json.length() == 0) {
+			logger.error(MessageFormat.format("Failed to receive json data from {0}",
+					request.getRemoteAddr()));
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+			return null;
+		}
+		return json.toString();
+	}
+	
+	private UserModel getUserOrAnonymous(HttpServletRequest r) {
+		UserModel user = (UserModel) r.getUserPrincipal();
+		if (user != null) { return user; }
+		return UserModel.ANONYMOUS;
+	}
+	
+	private static class UrlInfo {
+		public RepositoryModel repository;
+		public String oid;
+		public String baseUrl;
+		
+		public UrlInfo(RepositoryModel repo, String oid, String baseUrl) {
+			this.repository = repo;
+			this.oid = oid;
+			this.baseUrl = baseUrl;
+		}
+	}
+	
+	public static UrlInfo getInfoFromRequest(HttpServletRequest httpRequest) {
+		
+		String url = httpRequest.getRequestURL().toString();
+		Pattern p = Pattern.compile(REGEX_PATH);
+        Matcher m = p.matcher(url);
+		
+        
+        if (m.find()) {
+        	RepositoryModel repo = gitblit.getRepositoryModel(m.group(REGEX_GROUP_REPOSITORY));
+        	String baseUrl = m.group(REGEX_GROUP_BASE_URI) + "/" + m.group(REGEX_GROUP_PREFIX);
+        	
+        	if (m.group(REGEX_GROUP_ENDPOINT).equals("batch")) {
+        		return new UrlInfo(repo, null, baseUrl);
+        	} else {
+        		return new UrlInfo(repo, m.group(REGEX_GROUP_ENDPOINT), baseUrl);
+        	}
+        }
+		
+		return null;
+	}
+	
+	
+	public interface IGitLFS {
+	
+		@SuppressWarnings("serial")
+		public class Request implements Serializable
+		{
+			public String oid;
+			public long size;
+		}
+		
+		
+		@SuppressWarnings("serial")
+		public class Batch implements Serializable
+		{
+			public String operation;
+			public List<Request> objects;
+		}
+		
+		
+		@SuppressWarnings("serial")
+		public class Response implements Serializable
+		{
+			public String oid;
+			public long size;
+			public Map<String, HyperMediaLink> actions;
+			public ObjectError error;
+			public transient int successCode; 
+			
+			public Response(String id, long itemSize, int errorCode, String errorText) {
+				oid = id;
+				size = itemSize;
+				actions = null;
+				successCode = 0;
+				error = new ObjectError(errorCode, errorText);
+			}
+			
+			public Response(String id, long itemSize, int actionCode, String action, String uri) {
+				oid = id;
+				size = itemSize;
+				error = null;
+				successCode = actionCode;
+				actions = new HashMap<String, HyperMediaLink>();
+				actions.put(action, new HyperMediaLink(action, uri));
+			}
+			
+		}
+		
+		@SuppressWarnings("serial")
+		public class BatchResponse implements Serializable {
+			public List<Response> objects;
+			
+			public BatchResponse() {
+				objects = new ArrayList<Response>();
+			}
+		}
+		
+		
+		@SuppressWarnings("serial")
+		public class ObjectError implements Serializable
+		{
+			public String message;
+			public int code;
+			public String documentation_url;
+			public Integer request_id;
+			
+			public ObjectError(int errorCode, String errorText) {
+				code = errorCode;
+				message = errorText;
+				request_id = null;
+			}
+		}
+		
+		@SuppressWarnings("serial")
+		public class HyperMediaLink implements Serializable
+		{
+			public String href;
+			public transient String header;
+			//public Date expires_at;
+			
+			public HyperMediaLink(String action, String uri) {
+				header = action;
+				href = uri;
+			}
+		}
+	}
+
+
+	
+}
diff --git a/src/main/java/com/gitblit/servlet/GitFilter.java b/src/main/java/com/gitblit/servlet/GitFilter.java
index bb3d321..27408f0 100644
--- a/src/main/java/com/gitblit/servlet/GitFilter.java
+++ b/src/main/java/com/gitblit/servlet/GitFilter.java
@@ -17,7 +17,9 @@
 
 import java.text.MessageFormat;
 
-import javax.servlet.FilterConfig;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
 import javax.servlet.http.HttpServletRequest;
 
 import com.gitblit.Constants.AccessRestrictionType;
@@ -25,12 +27,13 @@
 import com.gitblit.GitBlitException;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
 
 /**
  * The GitFilter is an AccessRestrictionFilter which ensures that Git client
@@ -40,24 +43,34 @@
  * @author James Moger
  *
  */
+@Singleton
 public class GitFilter extends AccessRestrictionFilter {
 
 	protected static final String gitReceivePack = "/git-receive-pack";
 
 	protected static final String gitUploadPack = "/git-upload-pack";
-
+	
+	protected static final String gitLfs = "/info/lfs";
+	
 	protected static final String[] suffixes = { gitReceivePack, gitUploadPack, "/info/refs", "/HEAD",
-			"/objects" };
+			"/objects", gitLfs };
 
 	private IStoredSettings settings;
 
 	private IFederationManager federationManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
-		super.inject(dagger, filterConfig);
-		this.settings = dagger.get(IStoredSettings.class);
-		this.federationManager = dagger.get(IFederationManager.class);
+	@Inject
+	public GitFilter(
+			IStoredSettings settings,
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager,
+			IFederationManager federationManager) {
+
+		super(runtimeManager, authenticationManager, repositoryManager);
+
+		this.settings = settings;
+		this.federationManager = federationManager;
 	}
 
 	/**
@@ -106,6 +119,8 @@
 				return gitReceivePack;
 			} else if (suffix.contains("?service=git-upload-pack")) {
 				return gitUploadPack;
+			} else if (suffix.startsWith(gitLfs)) {
+				return gitLfs;
 			} else {
 				return gitUploadPack;
 			}
@@ -134,7 +149,13 @@
 	 * @return true if the server allows repository creation on-push
 	 */
 	@Override
-	protected boolean isCreationAllowed() {
+	protected boolean isCreationAllowed(String action) {
+		
+		//Repository must already exist before large files can be deposited
+		if (action.equals(gitLfs)) {
+			return false;
+		}
+		
 		return settings.getBoolean(Keys.git.allowCreateOnPush, true);
 	}
 
@@ -146,9 +167,15 @@
 	 * @return true if the action may be performed
 	 */
 	@Override
-	protected boolean isActionAllowed(RepositoryModel repository, String action) {
+	protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
 		// the log here has been moved into ReceiveHook to provide clients with
 		// error messages
+		if (gitLfs.equals(action)) {
+			if (!method.matches("GET|POST|PUT|HEAD")) {
+				return false;
+			}
+		}
+		
 		return true;
 	}
 
@@ -162,16 +189,25 @@
 	 *
 	 * @param repository
 	 * @param action
+	 * @param method
 	 * @return true if authentication required
 	 */
 	@Override
-	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+	protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
 		if (gitUploadPack.equals(action)) {
 			// send to client
 			return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
 		} else if (gitReceivePack.equals(action)) {
 			// receive from client
 			return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+		} else if (gitLfs.equals(action)) {
+			
+			if (method.matches("GET|HEAD")) {
+				return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
+			} else {
+				//NOTE: Treat POST as PUT as as without reading message type cannot determine 
+				return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
+			}
 		}
 		return false;
 	}
@@ -220,6 +256,12 @@
 	@Override
 	protected RepositoryModel createRepository(UserModel user, String repository, String action) {
 		boolean isPush = !StringUtils.isEmpty(action) && gitReceivePack.equals(action);
+		
+		if (action.equals(gitLfs)) {
+			//Repository must already exist for any filestore actions
+			return null;
+		}
+		
 		if (isPush) {
 			if (user.canCreate(repository)) {
 				// user is pushing to a new repository
@@ -271,4 +313,40 @@
 		// repository could not be created or action was not a push
 		return null;
 	}
+	
+	/**
+	 * Git lfs action uses an alternative authentication header, 
+	 * 
+	 * @param action
+	 * @return
+	 */
+	@Override
+	protected String getAuthenticationHeader(String action) {
+
+		if (action.equals(gitLfs)) {
+			return "LFS-Authenticate";
+		}
+		
+		return super.getAuthenticationHeader(action);
+	}
+	
+	/**
+	 * Interrogates the request headers based on the action
+	 * @param action
+	 * @param request
+	 * @return
+	 */
+	@Override
+	protected boolean hasValidRequestHeader(String action,
+			HttpServletRequest request) {
+
+		if (action.equals(gitLfs) && request.getMethod().equals("POST")) {
+			if ( 	!hasContentInRequestHeader(request, "Accept", FilestoreServlet.GIT_LFS_META_MIME)
+				 || !hasContentInRequestHeader(request, "Content-Type", FilestoreServlet.GIT_LFS_META_MIME)) {
+				return false;
+			}				
+		}
+			
+		return super.hasValidRequestHeader(action, request);
+	}
 }
diff --git a/src/main/java/com/gitblit/servlet/GitServlet.java b/src/main/java/com/gitblit/servlet/GitServlet.java
index 93fe31d..941b4c5 100644
--- a/src/main/java/com/gitblit/servlet/GitServlet.java
+++ b/src/main/java/com/gitblit/servlet/GitServlet.java
@@ -20,6 +20,8 @@
 import java.io.IOException;
 import java.util.Enumeration;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletConfig;
@@ -33,13 +35,10 @@
 
 import org.eclipse.jgit.http.server.GitFilter;
 
-import com.gitblit.dagger.DaggerContext;
 import com.gitblit.git.GitblitReceivePackFactory;
 import com.gitblit.git.GitblitUploadPackFactory;
 import com.gitblit.git.RepositoryResolver;
 import com.gitblit.manager.IGitblit;
-
-import dagger.ObjectGraph;
 
 /**
  * The GitServlet provides http/https access to Git repositories.
@@ -48,24 +47,23 @@
  * @author James Moger
  *
  */
+@Singleton
 public class GitServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
 	private final GitFilter gitFilter;
 
-	public GitServlet() {
+	@Inject
+	public GitServlet(IGitblit gitblit) {
 		gitFilter = new GitFilter();
+		gitFilter.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(gitblit));
+		gitFilter.setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>(gitblit));
+		gitFilter.setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>(gitblit));
 	}
 
 	@Override
 	public void init(final ServletConfig config) throws ServletException {
-		ServletContext context = config.getServletContext();
-		ObjectGraph dagger = (ObjectGraph) context.getAttribute(DaggerContext.INJECTOR_NAME);
-		IGitblit gitblit = dagger.get(IGitblit.class);
-		gitFilter.setRepositoryResolver(new RepositoryResolver<HttpServletRequest>(gitblit));
-		gitFilter.setUploadPackFactory(new GitblitUploadPackFactory<HttpServletRequest>(gitblit));
-		gitFilter.setReceivePackFactory(new GitblitReceivePackFactory<HttpServletRequest>(gitblit));
 
 		gitFilter.init(new FilterConfig() {
 			@Override
diff --git a/src/main/java/com/gitblit/servlet/GitblitContext.java b/src/main/java/com/gitblit/servlet/GitblitContext.java
index e5c59bd..fb8f6b9 100644
--- a/src/main/java/com/gitblit/servlet/GitblitContext.java
+++ b/src/main/java/com/gitblit/servlet/GitblitContext.java
@@ -31,16 +31,20 @@
 import javax.servlet.ServletContext;
 import javax.servlet.ServletContextEvent;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.gitblit.Constants;
-import com.gitblit.DaggerModule;
 import com.gitblit.FileSettings;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.WebXmlSettings;
-import com.gitblit.dagger.DaggerContext;
 import com.gitblit.extensions.LifeCycleListener;
+import com.gitblit.guice.CoreModule;
+import com.gitblit.guice.WebModule;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.IManager;
 import com.gitblit.manager.INotificationManager;
@@ -48,27 +52,33 @@
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
 import com.gitblit.manager.IUserManager;
+import com.gitblit.tickets.ITicketService;
 import com.gitblit.transport.ssh.IPublicKeyManager;
 import com.gitblit.utils.ContainerUtils;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.servlet.GuiceServletContextListener;
 
 /**
  * This class is the main entry point for the entire webapp.  It is a singleton
  * created manually by Gitblit GO or dynamically by the WAR/Express servlet
- * container.  This class instantiates and starts all managers.  Servlets and
- * filters are instantiated defined in web.xml and instantiated by the servlet
- * container, but those servlets and filters use Dagger to manually inject their
- * dependencies.
+ * container.  This class instantiates and starts all managers.
+ *
+ * Servlets and filters are injected which allows Gitblit to be completely
+ * code-driven.
  *
  * @author James Moger
  *
  */
-public class GitblitContext extends DaggerContext {
+public class GitblitContext extends GuiceServletContextListener {
 
 	private static GitblitContext gitblit;
+
+	protected final Logger logger = LoggerFactory.getLogger(getClass());
 
 	private final List<IManager> managers = new ArrayList<IManager>();
 
@@ -111,12 +121,16 @@
 		return null;
 	}
 
-	/**
-	 * Returns Gitblit's Dagger injection modules.
-	 */
 	@Override
-	protected Object [] getModules() {
-		return new Object [] { new DaggerModule() };
+	protected Injector getInjector() {
+		return Guice.createInjector(getModules());
+	}
+
+	/**
+	 * Returns Gitblit's Guice injection modules.
+	 */
+	protected AbstractModule [] getModules() {
+		return new AbstractModule [] { new CoreModule(), new WebModule() };
 	}
 
 	/**
@@ -127,18 +141,20 @@
 	 */
 	@Override
 	public final void contextInitialized(ServletContextEvent contextEvent) {
+		super.contextInitialized(contextEvent);
+
 		ServletContext context = contextEvent.getServletContext();
-		configureContext(context);
+		startCore(context);
 	}
 
 	/**
 	 * Prepare runtime settings and start all manager instances.
 	 */
-	protected void configureContext(ServletContext context) {
-		ObjectGraph injector = getInjector(context);
+	protected void startCore(ServletContext context) {
+		Injector injector = (Injector) context.getAttribute(Injector.class.getName());
 
 		// create the runtime settings object
-		IStoredSettings runtimeSettings = injector.get(IStoredSettings.class);
+		IStoredSettings runtimeSettings = injector.getInstance(IStoredSettings.class);
 		final File baseFolder;
 
 		if (goSettings != null) {
@@ -168,7 +184,7 @@
 
 		// Manually configure IRuntimeManager
 		logManager(IRuntimeManager.class);
-		IRuntimeManager runtime = injector.get(IRuntimeManager.class);
+		IRuntimeManager runtime = injector.getInstance(IRuntimeManager.class);
 		runtime.setBaseFolder(baseFolder);
 		runtime.getStatus().isGO = goSettings != null;
 		runtime.getStatus().servletContainer = context.getServerInfo();
@@ -186,7 +202,10 @@
 		startManager(injector, IRepositoryManager.class);
 		startManager(injector, IProjectManager.class);
 		startManager(injector, IFederationManager.class);
+		startManager(injector, ITicketService.class);
 		startManager(injector, IGitblit.class);
+		startManager(injector, IServicesManager.class);
+		startManager(injector, IFilestoreManager.class);
 
 		// start the plugin manager last so that plugins can depend on
 		// deterministic access to all other managers in their start() methods
@@ -196,7 +215,7 @@
 		logger.info("All managers started.");
 		logger.info("");
 
-		IPluginManager pluginManager = injector.get(IPluginManager.class);
+		IPluginManager pluginManager = injector.getInstance(IPluginManager.class);
 		for (LifeCycleListener listener : pluginManager.getExtensions(LifeCycleListener.class)) {
 			try {
 				listener.onStartup();
@@ -236,17 +255,21 @@
 		return defaultBaseFolder;
 	}
 
-	protected <X extends IManager> X loadManager(ObjectGraph injector, Class<X> clazz) {
-		X x = injector.get(clazz);
+	protected <X extends IManager> X loadManager(Injector injector, Class<X> clazz) {
+		X x = injector.getInstance(clazz);
 		return x;
 	}
 
-	protected <X extends IManager> X startManager(ObjectGraph injector, Class<X> clazz) {
+	protected <X extends IManager> X startManager(Injector injector, Class<X> clazz) {
 		X x = loadManager(injector, clazz);
 		logManager(clazz);
-		x.start();
-		managers.add(x);
-		return x;
+		return startManager(x);
+	}
+
+	protected <X extends IManager> X startManager(X x) {
+	    x.start();
+	    managers.add(x);
+	    return x;
 	}
 
 	protected void logManager(Class<? extends IManager> clazz) {
@@ -254,11 +277,17 @@
 		logger.info("----[{}]----", clazz.getName());
 	}
 
+	@Override
+	public final void contextDestroyed(ServletContextEvent contextEvent) {
+		super.contextDestroyed(contextEvent);
+		ServletContext context = contextEvent.getServletContext();
+		destroyContext(context);
+	}
+
 	/**
 	 * Gitblit is being shutdown either because the servlet container is
 	 * shutting down or because the servlet container is re-deploying Gitblit.
 	 */
-	@Override
 	protected void destroyContext(ServletContext context) {
 		logger.info("Gitblit context destroyed by servlet container.");
 
@@ -341,12 +370,10 @@
 		baseFolder.mkdirs();
 
 		// try to extract the data folder resource to the baseFolder
-		File localSettings = new File(baseFolder, "gitblit.properties");
-		if (!localSettings.exists()) {
-			extractResources(context, "/WEB-INF/data/", baseFolder);
-		}
+		extractResources(context, "/WEB-INF/data/", baseFolder);
 
 		// delegate all config to baseFolder/gitblit.properties file
+		File localSettings = new File(baseFolder, "gitblit.properties");
 		FileSettings fileSettings = new FileSettings(localSettings.getAbsolutePath());
 
 		// merge the stored settings into the runtime settings
diff --git a/src/main/java/com/gitblit/servlet/JsonServlet.java b/src/main/java/com/gitblit/servlet/JsonServlet.java
index 4378c8a..abc0f29 100644
--- a/src/main/java/com/gitblit/servlet/JsonServlet.java
+++ b/src/main/java/com/gitblit/servlet/JsonServlet.java
@@ -21,6 +21,7 @@
 import java.text.MessageFormat;
 
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -28,7 +29,6 @@
 import org.slf4j.LoggerFactory;
 
 import com.gitblit.Constants;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.utils.JsonUtils;
 import com.gitblit.utils.StringUtils;
 
@@ -38,7 +38,7 @@
  * @author James Moger
  *
  */
-public abstract class JsonServlet extends DaggerServlet {
+public abstract class JsonServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
diff --git a/src/main/java/com/gitblit/servlet/LogoServlet.java b/src/main/java/com/gitblit/servlet/LogoServlet.java
index 96f34af..d5d298b 100644
--- a/src/main/java/com/gitblit/servlet/LogoServlet.java
+++ b/src/main/java/com/gitblit/servlet/LogoServlet.java
@@ -21,16 +21,16 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRuntimeManager;
-
-import dagger.ObjectGraph;
 
 /**
  * Handles requests for logo.png
@@ -38,7 +38,8 @@
  * @author James Moger
  *
  */
-public class LogoServlet extends DaggerServlet {
+@Singleton
+public class LogoServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
@@ -46,9 +47,9 @@
 
 	private IRuntimeManager runtimeManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
+	@Inject
+	public LogoServlet(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/servlet/PagesFilter.java b/src/main/java/com/gitblit/servlet/PagesFilter.java
index e07d9b3..1d6c3db 100644
--- a/src/main/java/com/gitblit/servlet/PagesFilter.java
+++ b/src/main/java/com/gitblit/servlet/PagesFilter.java
@@ -15,6 +15,13 @@
  */
 package com.gitblit.servlet;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+
 
 /**
  * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
@@ -23,7 +30,17 @@
  * @author James Moger
  *
  */
+
+@Singleton
 public class PagesFilter extends RawFilter {
 
+	@Inject
+	public PagesFilter(
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager) {
+
+		super(runtimeManager, authenticationManager, repositoryManager);
+	}
 
 }
diff --git a/src/main/java/com/gitblit/servlet/PagesServlet.java b/src/main/java/com/gitblit/servlet/PagesServlet.java
index defd9e6..1473e96 100644
--- a/src/main/java/com/gitblit/servlet/PagesServlet.java
+++ b/src/main/java/com/gitblit/servlet/PagesServlet.java
@@ -1,103 +1,116 @@
-/*
- * Copyright 2012 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.servlet;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Date;
-
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.Constants;
-import com.gitblit.utils.JGitUtils;
-
-/**
- * Serves the content of a gh-pages branch.
- *
- * @author James Moger
- *
- */
-public class PagesServlet extends RawServlet {
-
-	private static final long serialVersionUID = 1L;
-
-
-	/**
-	 * Returns an url to this servlet for the specified parameters.
-	 *
-	 * @param baseURL
-	 * @param repository
-	 * @param path
-	 * @return an url
-	 */
-	public static String asLink(String baseURL, String repository, String path) {
-		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
-			baseURL = baseURL.substring(0, baseURL.length() - 1);
-		}
-		return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
-	}
-
-	@Override
-	protected String getBranch(String repository, HttpServletRequest request) {
-		return "gh-pages";
-	}
-
-	@Override
-	protected String getPath(String repository, String branch, HttpServletRequest request) {
-		String pi = request.getPathInfo().substring(1);
-		if (pi.equals(repository)) {
-			return "";
-		}
-		String path = pi.substring(pi.indexOf(repository) + repository.length() + 1);
-		if (path.endsWith("/")) {
-			path = path.substring(0, path.length() - 1);
-		}
-		return path;
-	}
-
-	@Override
-	protected boolean renderIndex() {
-		return true;
-	}
-
-	@Override
-	protected void setContentType(HttpServletResponse response, String contentType) {
-		response.setContentType(contentType);;
-	}
-
-	@Override
-	protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository,
-			RevCommit commit, String requestedPath) throws IOException {
-
-		response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
-		response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
-
-		return super.streamFromRepo(request, response, repository, commit, requestedPath);
-	}
-
-	@Override
-	protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
-		response.setDateHeader("Last-Modified", date.getTime());
-		response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
-
-		super.sendContent(response, date, is);
-	}
-}
+/*
+ * Copyright 2012 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.servlet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Date;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.Constants;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.utils.JGitUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+/**
+ * Serves the content of a gh-pages branch.
+ *
+ * @author James Moger
+ *
+ */
+@Singleton
+public class PagesServlet extends RawServlet {
+
+	private static final long serialVersionUID = 1L;
+
+
+	/**
+	 * Returns an url to this servlet for the specified parameters.
+	 *
+	 * @param baseURL
+	 * @param repository
+	 * @param path
+	 * @return an url
+	 */
+	public static String asLink(String baseURL, String repository, String path) {
+		if (baseURL.length() > 0 && baseURL.charAt(baseURL.length() - 1) == '/') {
+			baseURL = baseURL.substring(0, baseURL.length() - 1);
+		}
+		return baseURL + Constants.PAGES + repository + "/" + (path == null ? "" : ("/" + path));
+	}
+
+	@Inject
+	public PagesServlet(
+			IRuntimeManager runtimeManager,
+			IRepositoryManager repositoryManager) {
+
+		super(runtimeManager, repositoryManager);
+	}
+
+	@Override
+	protected String getBranch(String repository, HttpServletRequest request) {
+		return "gh-pages";
+	}
+
+	@Override
+	protected String getPath(String repository, String branch, HttpServletRequest request) {
+		String pi = request.getPathInfo().substring(1);
+		if (pi.equals(repository)) {
+			return "";
+		}
+		String path = pi.substring(pi.indexOf(repository) + repository.length() + 1);
+		if (path.endsWith("/")) {
+			path = path.substring(0, path.length() - 1);
+		}
+		return path;
+	}
+
+	@Override
+	protected boolean renderIndex() {
+		return true;
+	}
+
+	@Override
+	protected void setContentType(HttpServletResponse response, String contentType) {
+		response.setContentType(contentType);;
+	}
+
+	@Override
+	protected boolean streamFromRepo(HttpServletRequest request, HttpServletResponse response, Repository repository,
+			RevCommit commit, String requestedPath) throws IOException {
+
+		response.setDateHeader("Last-Modified", JGitUtils.getCommitDate(commit).getTime());
+		response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+
+		return super.streamFromRepo(request, response, repository, commit, requestedPath);
+	}
+
+	@Override
+	protected void sendContent(HttpServletResponse response, Date date, InputStream is) throws ServletException, IOException {
+		response.setDateHeader("Last-Modified", date.getTime());
+		response.setHeader("Cache-Control", "public, max-age=3600, must-revalidate");
+
+		super.sendContent(response, date, is);
+	}
+}
diff --git a/src/main/java/com/gitblit/servlet/ProxyFilter.java b/src/main/java/com/gitblit/servlet/ProxyFilter.java
index 46f59de..d7f096a 100644
--- a/src/main/java/com/gitblit/servlet/ProxyFilter.java
+++ b/src/main/java/com/gitblit/servlet/ProxyFilter.java
@@ -16,9 +16,13 @@
 package com.gitblit.servlet;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -27,12 +31,9 @@
 
 import ro.fortsoft.pf4j.PluginWrapper;
 
-import com.gitblit.dagger.DaggerFilter;
 import com.gitblit.extensions.HttpRequestFilter;
 import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IRuntimeManager;
-
-import dagger.ObjectGraph;
 
 /**
  * A request filter than allows registered extension request filters to access
@@ -41,15 +42,29 @@
  * @author David Ostrovsky
  * @since 1.6.0
  */
-public class ProxyFilter extends DaggerFilter {
-	private List<HttpRequestFilter> filters;
+@Singleton
+public class ProxyFilter implements Filter {
+	private final IRuntimeManager runtimeManager;
+
+	private final IPluginManager pluginManager;
+
+	private final List<HttpRequestFilter> filters;
+
+	@Inject
+	public ProxyFilter(
+			IRuntimeManager runtimeManager,
+			IPluginManager pluginManager) {
+
+		this.runtimeManager = runtimeManager;
+		this.pluginManager = pluginManager;
+		this.filters = new ArrayList<>();
+
+	}
 
 	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) throws ServletException {
-		IRuntimeManager runtimeManager = dagger.get(IRuntimeManager.class);
-		IPluginManager pluginManager = dagger.get(IPluginManager.class);
+	public void init(FilterConfig filterConfig) throws ServletException {
 
-		filters = pluginManager.getExtensions(HttpRequestFilter.class);
+		filters.addAll(pluginManager.getExtensions(HttpRequestFilter.class));
 		for (HttpRequestFilter f : filters) {
 			// wrap the filter config for Gitblit settings retrieval
 			PluginWrapper pluginWrapper = pluginManager.whichPlugin(f.getClass());
diff --git a/src/main/java/com/gitblit/servlet/PtServlet.java b/src/main/java/com/gitblit/servlet/PtServlet.java
index f69b444..5f577f8 100644
--- a/src/main/java/com/gitblit/servlet/PtServlet.java
+++ b/src/main/java/com/gitblit/servlet/PtServlet.java
@@ -22,7 +22,10 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -34,10 +37,7 @@
 import org.apache.commons.compress.compressors.CompressorStreamFactory;
 import org.eclipse.jgit.lib.FileMode;
 
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRuntimeManager;
-
-import dagger.ObjectGraph;
 
 /**
  * Handles requests for the Barnum pt (patchset tool).
@@ -47,7 +47,8 @@
  * @author James Moger
  *
  */
-public class PtServlet extends DaggerServlet {
+@Singleton
+public class PtServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
@@ -55,9 +56,9 @@
 
 	private IRuntimeManager runtimeManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
+	@Inject
+	public PtServlet(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/servlet/RawFilter.java b/src/main/java/com/gitblit/servlet/RawFilter.java
index 34989c9..8913a19 100644
--- a/src/main/java/com/gitblit/servlet/RawFilter.java
+++ b/src/main/java/com/gitblit/servlet/RawFilter.java
@@ -15,9 +15,15 @@
  */
 package com.gitblit.servlet;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
 import org.eclipse.jgit.lib.Repository;
 
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 
@@ -28,7 +34,17 @@
  * @author James Moger
  *
  */
+@Singleton
 public class RawFilter extends AccessRestrictionFilter {
+
+	@Inject
+	public RawFilter(
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager) {
+
+		super(runtimeManager, authenticationManager, repositoryManager);
+	}
 
 	/**
 	 * Extract the repository name from the url.
@@ -82,7 +98,7 @@
 	 * @return true if the filter allows repository creation
 	 */
 	@Override
-	protected boolean isCreationAllowed() {
+	protected boolean isCreationAllowed(String action) {
 		return false;
 	}
 
@@ -91,10 +107,11 @@
 	 *
 	 * @param repository
 	 * @param action
+	 * @param method
 	 * @return true if the action may be performed
 	 */
 	@Override
-	protected boolean isActionAllowed(RepositoryModel repository, String action) {
+	protected boolean isActionAllowed(RepositoryModel repository, String action, String method) {
 		return true;
 	}
 
@@ -103,10 +120,11 @@
 	 *
 	 * @param repository
 	 * @param action
+	 * @param method
 	 * @return true if authentication required
 	 */
 	@Override
-	protected boolean requiresAuthentication(RepositoryModel repository, String action) {
+	protected boolean requiresAuthentication(RepositoryModel repository, String action, String method) {
 		return repository.accessRestriction.atLeast(AccessRestrictionType.VIEW);
 	}
 
diff --git a/src/main/java/com/gitblit/servlet/RawServlet.java b/src/main/java/com/gitblit/servlet/RawServlet.java
index ca41d0a..1d2724b 100644
--- a/src/main/java/com/gitblit/servlet/RawServlet.java
+++ b/src/main/java/com/gitblit/servlet/RawServlet.java
@@ -24,12 +24,14 @@
 import java.text.ParseException;
 import java.util.ArrayList;
 import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
 
 import javax.servlet.ServletContext;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -48,7 +50,6 @@
 
 import com.gitblit.Constants;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.PathModel;
@@ -56,8 +57,8 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Serves the content of a branch.
@@ -65,20 +66,24 @@
  * @author James Moger
  *
  */
-public class RawServlet extends DaggerServlet {
+@Singleton
+public class RawServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
 	private transient Logger logger = LoggerFactory.getLogger(RawServlet.class);
 
-	private IRuntimeManager runtimeManager;
+	private final IRuntimeManager runtimeManager;
 
-	private IRepositoryManager repositoryManager;
+	private final IRepositoryManager repositoryManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
+	@Inject
+	public RawServlet(
+			IRuntimeManager runtimeManager,
+			IRepositoryManager repositoryManager) {
+
+		this.runtimeManager = runtimeManager;
+		this.repositoryManager = repositoryManager;
 	}
 
 	/**
@@ -224,15 +229,32 @@
 				return;
 			}
 
+			Map<String, String> quickContentTypes = new HashMap<>();
+			quickContentTypes.put("html", "text/html");
+			quickContentTypes.put("htm", "text/html");
+			quickContentTypes.put("xml", "application/xml");
+			quickContentTypes.put("json", "application/json");
 
 			List<PathModel> pathEntries = JGitUtils.getFilesInPath(r, requestedPath, commit);
 			if (pathEntries.isEmpty()) {
 				// requested a specific resource
 				String file = StringUtils.getLastPathElement(requestedPath);
 				try {
-					// query Tika for the content type
-					Tika tika = new Tika();
-					String contentType = tika.detect(file);
+
+					String ext = StringUtils.getFileExtension(file).toLowerCase();
+					String contentType = quickContentTypes.get(ext);
+
+					if (contentType == null) {
+						List<String> exts = runtimeManager.getSettings().getStrings(Keys.web.prettyPrintExtensions);
+						if (exts.contains(ext)) {
+							// extension is a registered text type for pretty printing
+							contentType = "text/plain";
+						} else {
+							// query Tika for the content type
+							Tika tika = new Tika();
+							contentType = tika.detect(file);
+						}
+					}
 
 					if (contentType == null) {
 						// ask the container for the content type
@@ -244,7 +266,7 @@
 						}
 					}
 
-					if (isTextType(contentType)) {
+					if (isTextType(contentType) || isTextDataType(contentType)) {
 
 						// load, interpret, and serve text content as UTF-8
 						String [] encodings = runtimeManager.getSettings().getStrings(Keys.web.blobEncodings).toArray(new String[0]);
@@ -378,6 +400,13 @@
 		return false;
 	}
 
+	protected boolean isTextDataType(String contentType) {
+		if ("image/svg+xml".equals(contentType)) {
+			return true;
+		}
+		return false;
+	}
+
 	/**
 	 * Override all text types to be plain text.
 	 *
@@ -439,7 +468,7 @@
 				served = true;
 			}
 		} finally {
-			tw.release();
+			tw.close();
 			rw.dispose();
 		}
 
diff --git a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java
index 9bd3b3c..4e58d4d 100644
--- a/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java
+++ b/src/main/java/com/gitblit/servlet/RobotsTxtServlet.java
@@ -18,16 +18,16 @@
 import java.io.File;
 import java.io.IOException;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.utils.FileUtils;
-
-import dagger.ObjectGraph;
 
 /**
  * Handles requests for robots.txt
@@ -35,15 +35,16 @@
  * @author James Moger
  *
  */
-public class RobotsTxtServlet extends DaggerServlet {
+@Singleton
+public class RobotsTxtServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
 	private IRuntimeManager runtimeManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
+	@Inject
+	public RobotsTxtServlet(IRuntimeManager runtimeManager) {
+		this.runtimeManager = runtimeManager;
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/servlet/RpcFilter.java b/src/main/java/com/gitblit/servlet/RpcFilter.java
index 23bf956..34474d5 100644
--- a/src/main/java/com/gitblit/servlet/RpcFilter.java
+++ b/src/main/java/com/gitblit/servlet/RpcFilter.java
@@ -18,8 +18,9 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
@@ -29,10 +30,9 @@
 import com.gitblit.Constants.RpcRequest;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.UserModel;
-
-import dagger.ObjectGraph;
 
 /**
  * The RpcFilter is a servlet filter that secures the RpcServlet.
@@ -47,17 +47,23 @@
  * @author James Moger
  *
  */
+@Singleton
 public class RpcFilter extends AuthenticationFilter {
 
 	private IStoredSettings settings;
 
 	private IRuntimeManager runtimeManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
-		super.inject(dagger, filterConfig);
-		this.settings = dagger.get(IStoredSettings.class);
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
+	@Inject
+	public RpcFilter(
+			IStoredSettings settings,
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager) {
+
+		super(authenticationManager);
+
+		this.settings = settings;
+		this.runtimeManager = runtimeManager;
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/servlet/RpcServlet.java b/src/main/java/com/gitblit/servlet/RpcServlet.java
index b8cdfb0..9809a25 100644
--- a/src/main/java/com/gitblit/servlet/RpcServlet.java
+++ b/src/main/java/com/gitblit/servlet/RpcServlet.java
@@ -23,6 +23,8 @@
 import java.util.List;
 import java.util.Map;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
@@ -47,13 +49,12 @@
 import com.gitblit.utils.RpcUtils;
 import com.gitblit.utils.StringUtils;
 
-import dagger.ObjectGraph;
-
 /**
  * Handles remote procedure calls.
  *
  * @author James Moger
  */
+@Singleton
 public class RpcServlet extends JsonServlet {
 
 	private static final long serialVersionUID = 1L;
@@ -64,10 +65,10 @@
 
 	private IGitblit gitblit;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.gitblit = dagger.get(IGitblit.class);
+	@Inject
+	public RpcServlet(IStoredSettings settings, IGitblit gitblit) {
+		this.settings = settings;
+		this.gitblit = gitblit;
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
index 150dd68..e989ece 100644
--- a/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
+++ b/src/main/java/com/gitblit/servlet/SparkleShareInviteServlet.java
@@ -20,20 +20,20 @@
 import java.text.MessageFormat;
 
 import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IUserManager;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Handles requests for Sparkleshare Invites
@@ -41,7 +41,8 @@
  * @author James Moger
  *
  */
-public class SparkleShareInviteServlet extends DaggerServlet {
+@Singleton
+public class SparkleShareInviteServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
@@ -53,12 +54,17 @@
 
 	private IRepositoryManager repositoryManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.userManager = dagger.get(IUserManager.class);
-		this.authenticationManager = dagger.get(IAuthenticationManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
+	@Inject
+	public SparkleShareInviteServlet(
+			IStoredSettings settings,
+			IUserManager userManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager) {
+
+		this.settings = settings;
+		this.userManager = userManager;
+		this.authenticationManager = authenticationManager;
+		this.repositoryManager = repositoryManager;
 	}
 
 	@Override
@@ -83,6 +89,8 @@
 			response.getWriter().append("SSH is not active on this server!");
 			return;
 		}
+		int sshDisplayPort = settings.getInteger(Keys.git.sshAdvertisedPort, sshPort);
+
 		// extract repo name from request
 		String repoUrl = request.getPathInfo().substring(1);
 
@@ -105,6 +113,10 @@
 		String url = settings.getString(Keys.web.canonicalUrl, "https://localhost:8443");
 		if (!StringUtils.isEmpty(url) && url.indexOf("localhost") == -1) {
 			host = new URL(url).getHost();
+		}
+		String sshDisplayHost = settings.getString(Keys.git.sshAdvertisedHost, "");
+		if(sshDisplayHost.isEmpty()) {
+			sshDisplayHost = host;
 		}
 
 		UserModel user;
@@ -135,7 +147,7 @@
 		StringBuilder sb = new StringBuilder();
 		sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
 		sb.append("<sparkleshare><invite>\n");
-		sb.append(MessageFormat.format("<address>ssh://{0}@{1}:{2,number,0}/</address>\n", user.username, host, sshPort));
+		sb.append(MessageFormat.format("<address>ssh://{0}@{1}:{2,number,0}/</address>\n", user.username, sshDisplayHost, sshDisplayPort));
 		sb.append(MessageFormat.format("<remote_path>/{0}</remote_path>\n", model.name));
 		int fanoutPort = settings.getInteger(Keys.fanout.port, 0);
 		if (fanoutPort > 0) {
diff --git a/src/main/java/com/gitblit/servlet/SyndicationFilter.java b/src/main/java/com/gitblit/servlet/SyndicationFilter.java
index 78da47e..49348d0 100644
--- a/src/main/java/com/gitblit/servlet/SyndicationFilter.java
+++ b/src/main/java/com/gitblit/servlet/SyndicationFilter.java
@@ -18,8 +18,9 @@
 import java.io.IOException;
 import java.text.MessageFormat;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
@@ -27,14 +28,13 @@
 import javax.servlet.http.HttpServletResponse;
 
 import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
-
-import dagger.ObjectGraph;
 
 /**
  * The SyndicationFilter is an AuthenticationFilter which ensures that feed
@@ -44,18 +44,24 @@
  * @author James Moger
  *
  */
+@Singleton
 public class SyndicationFilter extends AuthenticationFilter {
 
 	private IRuntimeManager runtimeManager;
 	private IRepositoryManager repositoryManager;
 	private IProjectManager projectManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger, FilterConfig filterConfig) {
-		super.inject(dagger, filterConfig);
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
-		this.projectManager = dagger.get(IProjectManager.class);
+	@Inject
+	public SyndicationFilter(
+			IRuntimeManager runtimeManager,
+			IAuthenticationManager authenticationManager,
+			IRepositoryManager repositoryManager,
+			IProjectManager projectManager) {
+		super(authenticationManager);
+
+		this.runtimeManager = runtimeManager;
+		this.repositoryManager = repositoryManager;
+		this.projectManager = projectManager;
 	}
 
 	/**
diff --git a/src/main/java/com/gitblit/servlet/SyndicationServlet.java b/src/main/java/com/gitblit/servlet/SyndicationServlet.java
index e3c2596..39dbf2e 100644
--- a/src/main/java/com/gitblit/servlet/SyndicationServlet.java
+++ b/src/main/java/com/gitblit/servlet/SyndicationServlet.java
@@ -22,6 +22,8 @@
 import java.util.List;
 import java.util.Map;
 
+import javax.servlet.http.HttpServlet;
+
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -31,7 +33,6 @@
 import com.gitblit.Constants;
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerServlet;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.models.FeedEntryModel;
@@ -45,8 +46,8 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.SyndicationUtils;
-
-import dagger.ObjectGraph;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * SyndicationServlet generates RSS 2.0 feeds and feed links.
@@ -56,7 +57,8 @@
  * @author James Moger
  *
  */
-public class SyndicationServlet extends DaggerServlet {
+@Singleton
+public class SyndicationServlet extends HttpServlet {
 
 	private static final long serialVersionUID = 1L;
 
@@ -68,11 +70,15 @@
 
 	private IProjectManager projectManager;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
-		this.projectManager = dagger.get(IProjectManager.class);
+	@Inject
+	public SyndicationServlet(
+			IStoredSettings settings,
+			IRepositoryManager repositoryManager,
+			IProjectManager projectManager) {
+
+		this.settings = settings;
+		this.repositoryManager = repositoryManager;
+		this.projectManager = projectManager;
 	}
 
 	/**
@@ -362,7 +368,7 @@
 			if (mountParameters) {
 				// mounted url
 				feedLink = MessageFormat.format("{0}/summary/{1}", gitblitUrl,
-						StringUtils.encodeURL(feedName));
+						StringUtils.encodeURL(feedName.replace('/', fsc)));
 			} else {
 				// parameterized url
 				feedLink = MessageFormat.format("{0}/summary/?r={1}", gitblitUrl,
diff --git a/src/main/java/com/gitblit/tickets/BranchTicketService.java b/src/main/java/com/gitblit/tickets/BranchTicketService.java
index 5a42c6a..8396693 100644
--- a/src/main/java/com/gitblit/tickets/BranchTicketService.java
+++ b/src/main/java/com/gitblit/tickets/BranchTicketService.java
@@ -72,6 +72,8 @@
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Implementation of a ticket service based on an orphan branch.  All tickets
@@ -81,6 +83,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class BranchTicketService extends ITicketService implements RefsChangedListener {
 
 	public static final String BRANCH = "refs/meta/gitblit/tickets";
@@ -91,6 +94,7 @@
 
 	private final Map<String, AtomicLong> lastAssignedId;
 
+	@Inject
 	public BranchTicketService(
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
@@ -112,6 +116,7 @@
 
 	@Override
 	public BranchTicketService start() {
+		log.info("{} started", getClass().getSimpleName());
 		return this;
 	}
 
@@ -292,7 +297,7 @@
 			log.error("failed to read " + file, e);
 		} finally {
 			if (rw != null) {
-				rw.release();
+				rw.close();
 			}
 		}
 		return null;
@@ -348,7 +353,7 @@
 		} catch (IOException e) {
 			log.error("", e);
 		} finally {
-			inserter.release();
+			inserter.close();
 		}
 	}
 
@@ -707,7 +712,7 @@
 			} finally {
 				// release the treewalk
 				if (treeWalk != null) {
-					treeWalk.release();
+					treeWalk.close();
 				}
 			}
 		} finally {
@@ -806,7 +811,7 @@
 			// finish the index
 			builder.finish();
 		} finally {
-			inserter.release();
+			inserter.close();
 		}
 		return newIndex;
 	}
@@ -850,7 +855,7 @@
 			}
 		} finally {
 			if (tw != null) {
-				tw.release();
+				tw.close();
 			}
 		}
 		return list;
@@ -908,10 +913,10 @@
 							rc));
 				}
 			} finally {
-				revWalk.release();
+				revWalk.close();
 			}
 		} finally {
-			odi.release();
+			odi.close();
 		}
 		return success;
 	}
diff --git a/src/main/java/com/gitblit/tickets/FileTicketService.java b/src/main/java/com/gitblit/tickets/FileTicketService.java
index b3d8838..1e82f0d 100644
--- a/src/main/java/com/gitblit/tickets/FileTicketService.java
+++ b/src/main/java/com/gitblit/tickets/FileTicketService.java
@@ -42,6 +42,8 @@
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.FileUtils;
 import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Implementation of a ticket service based on a directory within the repository.
@@ -51,6 +53,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class FileTicketService extends ITicketService {
 
 	private static final String JOURNAL = "journal.json";
@@ -59,6 +62,7 @@
 
 	private final Map<String, AtomicLong> lastAssignedId;
 
+	@Inject
 	public FileTicketService(
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
@@ -77,6 +81,7 @@
 
 	@Override
 	public FileTicketService start() {
+		log.info("{} started", getClass().getSimpleName());
 		return this;
 	}
 
@@ -488,6 +493,10 @@
 	@Override
 	protected boolean deleteAllImpl(RepositoryModel repository) {
 		Repository db = repositoryManager.getRepository(repository.name);
+		if (db == null) {
+			// the tickets no longer exist because the db no longer exists
+			return true;
+		}
 		try {
 			File dir = new File(db.getDirectory(), TICKETS_PATH);
 			return FileUtils.delete(dir);
diff --git a/src/main/java/com/gitblit/tickets/ITicketService.java b/src/main/java/com/gitblit/tickets/ITicketService.java
index 4cf099f..5e3e372 100644
--- a/src/main/java/com/gitblit/tickets/ITicketService.java
+++ b/src/main/java/com/gitblit/tickets/ITicketService.java
@@ -36,6 +36,7 @@
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.extensions.TicketHook;
+import com.gitblit.manager.IManager;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IRepositoryManager;
@@ -63,7 +64,7 @@
  * @author James Moger
  *
  */
-public abstract class ITicketService {
+public abstract class ITicketService implements IManager {
 
 	public static final String SETTING_UPDATE_DIFFSTATS = "migration.updateDiffstats";
 
@@ -176,12 +177,14 @@
 	 * Start the service.
 	 * @since 1.4.0
 	 */
+	@Override
 	public abstract ITicketService start();
 
 	/**
 	 * Stop the service.
 	 * @since 1.4.0
 	 */
+	@Override
 	public final ITicketService stop() {
 		indexer.close();
 		ticketsCache.invalidateAll();
diff --git a/src/main/java/com/gitblit/tickets/NullTicketService.java b/src/main/java/com/gitblit/tickets/NullTicketService.java
index d410cdd..3947b94 100644
--- a/src/main/java/com/gitblit/tickets/NullTicketService.java
+++ b/src/main/java/com/gitblit/tickets/NullTicketService.java
@@ -28,6 +28,8 @@
 import com.gitblit.models.TicketModel;
 import com.gitblit.models.TicketModel.Attachment;
 import com.gitblit.models.TicketModel.Change;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Implementation of a ticket service that rejects everything.
@@ -35,8 +37,10 @@
  * @author James Moger
  *
  */
+@Singleton
 public class NullTicketService extends ITicketService {
 
+	@Inject
 	public NullTicketService(
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
@@ -58,6 +62,7 @@
 
 	@Override
 	public NullTicketService start() {
+		log.info("{} started", getClass().getSimpleName());
 		return this;
 	}
 
diff --git a/src/main/java/com/gitblit/tickets/QueryResult.java b/src/main/java/com/gitblit/tickets/QueryResult.java
index 7a2b1ab..f8d6d12 100644
--- a/src/main/java/com/gitblit/tickets/QueryResult.java
+++ b/src/main/java/com/gitblit/tickets/QueryResult.java
@@ -24,6 +24,8 @@
 import com.gitblit.models.TicketModel.Patchset;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.models.TicketModel.Type;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
 import com.gitblit.utils.StringUtils;
 
 /**
@@ -62,6 +64,8 @@
 	public int commentsCount;
 	public int votesCount;
 	public int approvalsCount;
+	public Priority priority;
+	public Severity severity;
 
 	public int docId;
 	public int totalResults;
diff --git a/src/main/java/com/gitblit/tickets/RedisTicketService.java b/src/main/java/com/gitblit/tickets/RedisTicketService.java
index d773b0b..0f9ad17 100644
--- a/src/main/java/com/gitblit/tickets/RedisTicketService.java
+++ b/src/main/java/com/gitblit/tickets/RedisTicketService.java
@@ -43,6 +43,8 @@
 import com.gitblit.models.TicketModel.Change;
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.StringUtils;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 
 /**
  * Implementation of a ticket service based on a Redis key-value store.  All
@@ -53,6 +55,7 @@
  * @author James Moger
  *
  */
+@Singleton
 public class RedisTicketService extends ITicketService {
 
 	private final JedisPool pool;
@@ -61,6 +64,7 @@
 		journal, ticket, counter
 	}
 
+	@Inject
 	public RedisTicketService(
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
@@ -80,6 +84,10 @@
 
 	@Override
 	public RedisTicketService start() {
+		log.info("{} started", getClass().getSimpleName());
+		if (!isReady()) {
+			log.warn("{} is not ready!", getClass().getSimpleName());
+		}
 		return this;
 	}
 
diff --git a/src/main/java/com/gitblit/tickets/TicketIndexer.java b/src/main/java/com/gitblit/tickets/TicketIndexer.java
index 11ea3a7..e2d53af 100644
--- a/src/main/java/com/gitblit/tickets/TicketIndexer.java
+++ b/src/main/java/com/gitblit/tickets/TicketIndexer.java
@@ -103,7 +103,10 @@
 		mergesha(Type.STRING),
 		mergeto(Type.STRING),
 		patchsets(Type.INT),
-		votes(Type.INT);
+		votes(Type.INT),
+		//NOTE: Indexing on the underlying value to allow flexibility on naming
+		priority(Type.INT),
+		severity(Type.INT);
 
 		final Type fieldType;
 
@@ -519,6 +522,8 @@
 		toDocField(doc, Lucene.watchedby, StringUtils.flattenStrings(ticket.getWatchers(), ";").toLowerCase());
 		toDocField(doc, Lucene.mentions, StringUtils.flattenStrings(ticket.getMentions(), ";").toLowerCase());
 		toDocField(doc, Lucene.votes, ticket.getVoters().size());
+		toDocField(doc, Lucene.priority, ticket.priority.getValue());
+		toDocField(doc, Lucene.severity, ticket.severity.getValue());
 
 		List<String> attachments = new ArrayList<String>();
 		for (Attachment attachment : ticket.getAttachments()) {
@@ -600,6 +605,8 @@
 		result.participants = unpackStrings(doc, Lucene.participants);
 		result.watchedby = unpackStrings(doc, Lucene.watchedby);
 		result.mentions = unpackStrings(doc, Lucene.mentions);
+		result.priority = TicketModel.Priority.fromObject(unpackInt(doc, Lucene.priority), TicketModel.Priority.defaultPriority);
+		result.severity = TicketModel.Severity.fromObject(unpackInt(doc, Lucene.severity), TicketModel.Severity.defaultSeverity);
 
 		if (!StringUtils.isEmpty(doc.get(Lucene.patchset.name()))) {
 			// unpack most recent patchset
diff --git a/src/main/java/com/gitblit/tickets/TicketNotifier.java b/src/main/java/com/gitblit/tickets/TicketNotifier.java
index d6217b3..5979cf2 100644
--- a/src/main/java/com/gitblit/tickets/TicketNotifier.java
+++ b/src/main/java/com/gitblit/tickets/TicketNotifier.java
@@ -135,6 +135,7 @@
 			StringBuilder html = new StringBuilder();
 			html.append("<head>");
 			html.append(readStyle());
+			html.append(readViewTicketAction(ticket));
 			html.append("</head>");
 			html.append("<body>");
 			html.append(MarkdownUtils.transformGFM(settings, markdown, ticket.repository));
@@ -613,6 +614,12 @@
 		return sb.toString();
 	}
 
+	protected String readViewTicketAction(TicketModel ticket) {
+		String action = readResource("viewTicket.html");
+		action = action.replace("${url}", ticketService.getTicketUrl(ticket));
+		return action;
+	}
+
 	protected String readResource(String resource) {
 		StringBuilder sb = new StringBuilder();
 		InputStream is = null;
diff --git a/src/main/java/com/gitblit/tickets/viewTicket.html b/src/main/java/com/gitblit/tickets/viewTicket.html
new file mode 100644
index 0000000..54e091c
--- /dev/null
+++ b/src/main/java/com/gitblit/tickets/viewTicket.html
@@ -0,0 +1,12 @@
+<script type="application/ld+json">
+{
+  "@context": "http://schema.org",
+  "@type": "EmailMessage",
+  "description": "View this Ticket in Gitblit",
+  "action": {
+    "@type": "ViewAction",
+    "url": "${url}",
+    "name": "View Ticket"
+  }
+}
+</script>
diff --git a/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java b/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java
index de661a4..9bab3b8 100644
--- a/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java
+++ b/src/main/java/com/gitblit/transport/ssh/DisabledFilesystemFactory.java
@@ -16,31 +16,22 @@
 package com.gitblit.transport.ssh;
 
 import java.io.IOException;
+import java.nio.file.FileSystem;
 
-import org.apache.sshd.common.Session;
 import org.apache.sshd.common.file.FileSystemFactory;
-import org.apache.sshd.common.file.FileSystemView;
-import org.apache.sshd.common.file.SshFile;
+import org.apache.sshd.common.session.Session;
 
 public class DisabledFilesystemFactory implements FileSystemFactory {
 
-	@Override
-	public FileSystemView createFileSystemView(Session session) throws IOException {
-		return new FileSystemView() {
-			@Override
-			public SshFile getFile(SshFile baseDir, String file) {
-				return null;
-			}
-
-			@Override
-			public SshFile getFile(String file) {
-				return null;
-			}
-
-			@Override
-			public FileSystemView getNormalizedView() {
-				return null;
-			}
-		};
-	}
+	 /**
+     * Create user specific file system.
+     *
+     * @param session The session created for the user
+     * @return The current {@link FileSystem} for the provided session
+     * @throws java.io.IOException when the filesystem can not be created
+     */
+    @Override
+	public FileSystem createFileSystem(Session session) throws IOException {
+    	return null;
+    }
 }
diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
index a063dc7..1a2cd68 100644
--- a/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
+++ b/src/main/java/com/gitblit/transport/ssh/FileKeyManager.java
@@ -29,6 +29,7 @@
 import com.google.common.base.Charsets;
 import com.google.common.base.Joiner;
 import com.google.common.io.Files;
+import com.google.inject.Inject;
 
 /**
  * Manages public keys on the filesystem.
@@ -42,6 +43,7 @@
 
 	protected final Map<File, Long> lastModifieds;
 
+	@Inject
 	public FileKeyManager(IRuntimeManager runtimeManager) {
 		this.runtimeManager = runtimeManager;
 		this.lastModifieds = new ConcurrentHashMap<File, Long>();
diff --git a/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
new file mode 100644
index 0000000..db0741e
--- /dev/null
+++ b/src/main/java/com/gitblit/transport/ssh/FileKeyPairProvider.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.transport.ssh;
+
+import java.io.FileInputStream;
+import java.io.InputStreamReader;
+import java.security.KeyPair;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
+import org.apache.sshd.common.util.SecurityUtils;
+import org.bouncycastle.openssl.PEMDecryptorProvider;
+import org.bouncycastle.openssl.PEMEncryptedKeyPair;
+import org.bouncycastle.openssl.PEMKeyPair;
+import org.bouncycastle.openssl.PEMParser;
+import org.bouncycastle.openssl.PasswordFinder;
+import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
+import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
+
+/**
+ * This host key provider loads private keys from the specified files.
+ *
+ * Note that this class has a direct dependency on BouncyCastle and won't work
+ * unless it has been correctly registered as a security provider.
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class FileKeyPairProvider extends AbstractKeyPairProvider {
+
+    private String[] files;
+    private PasswordFinder passwordFinder;
+
+    public FileKeyPairProvider() {
+    }
+
+    public FileKeyPairProvider(String[] files) {
+        this.files = files;
+    }
+
+    public FileKeyPairProvider(String[] files, PasswordFinder passwordFinder) {
+        this.files = files;
+        this.passwordFinder = passwordFinder;
+    }
+
+    public String[] getFiles() {
+        return files;
+    }
+
+    public void setFiles(String[] files) {
+        this.files = files;
+    }
+
+    public PasswordFinder getPasswordFinder() {
+        return passwordFinder;
+    }
+
+    public void setPasswordFinder(PasswordFinder passwordFinder) {
+        this.passwordFinder = passwordFinder;
+    }
+
+    public Iterable<KeyPair> loadKeys() {
+        if (!SecurityUtils.isBouncyCastleRegistered()) {
+            throw new IllegalStateException("BouncyCastle must be registered as a JCE provider");
+        }
+        return new Iterable<KeyPair>() {
+            @Override
+			public Iterator<KeyPair> iterator() {
+                return new Iterator<KeyPair>() {
+                    private final Iterator<String> iterator = Arrays.asList(files).iterator();
+                    private KeyPair nextKeyPair;
+                    private boolean nextKeyPairSet = false;
+                    @Override
+					public boolean hasNext() {
+                        return nextKeyPairSet || setNextObject();
+                    }
+                    @Override
+					public KeyPair next() {
+                        if (!nextKeyPairSet) {
+                            if (!setNextObject()) {
+                                throw new NoSuchElementException();
+                            }
+                        }
+                        nextKeyPairSet = false;
+                        return nextKeyPair;
+                    }
+                    @Override
+					public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                    private boolean setNextObject() {
+                        while (iterator.hasNext()) {
+                            String file = iterator.next();
+                            nextKeyPair = doLoadKey(file);
+                            if (nextKeyPair != null) {
+                                nextKeyPairSet = true;
+                                return true;
+                            }
+                        }
+                        return false;
+                    }
+
+                };
+            }
+        };
+    }
+
+    protected KeyPair doLoadKey(String file) {
+        try {
+            PEMParser r = new PEMParser(new InputStreamReader(new FileInputStream(file)));
+            try {
+                Object o = r.readObject();
+
+                JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter();
+                pemConverter.setProvider("BC");
+                if (passwordFinder != null && o instanceof PEMEncryptedKeyPair) {
+                    JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder();
+                    PEMDecryptorProvider pemDecryptor = decryptorBuilder.build(passwordFinder.getPassword());
+                    o = pemConverter.getKeyPair(((PEMEncryptedKeyPair) o).decryptKeyPair(pemDecryptor));
+                }
+
+                if (o instanceof PEMKeyPair) {
+                    o = pemConverter.getKeyPair((PEMKeyPair)o);
+                    return (KeyPair) o;
+                } else if (o instanceof KeyPair) {
+                    return (KeyPair) o;
+                }
+            } finally {
+                r.close();
+            }
+        } catch (Exception e) {
+            log.warn("Unable to read key " + file, e);
+        }
+        return null;
+    }
+
+}
diff --git a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
index 357b34a..bf78378 100644
--- a/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
+++ b/src/main/java/com/gitblit/transport/ssh/MemoryKeyManager.java
@@ -20,6 +20,8 @@
 import java.util.List;
 import java.util.Map;
 
+import com.google.inject.Inject;
+
 /**
  * Memory public key manager.
  *
@@ -30,6 +32,7 @@
 
 	final Map<String, List<SshKey>> keys;
 
+	@Inject
 	public MemoryKeyManager() {
 		keys = new HashMap<String, List<SshKey>>();
 	}
diff --git a/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java b/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java
index 4bd75d5..29f7750 100644
--- a/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java
+++ b/src/main/java/com/gitblit/transport/ssh/NonForwardingFilter.java
@@ -15,13 +15,14 @@
  */
 package com.gitblit.transport.ssh;
 
-import org.apache.sshd.common.ForwardingFilter;
-import org.apache.sshd.common.Session;
 import org.apache.sshd.common.SshdSocketAddress;
+import org.apache.sshd.common.session.Session;
+import org.apache.sshd.server.forward.ForwardingFilter;
 
 public class NonForwardingFilter implements ForwardingFilter {
+
 	@Override
-	public boolean canConnect(SshdSocketAddress address, Session session) {
+	public boolean canConnect(Type type, SshdSocketAddress address, Session session) {
 		return false;
 	}
 
diff --git a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
index 0761d84..fcd3e19 100644
--- a/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
+++ b/src/main/java/com/gitblit/transport/ssh/NullKeyManager.java
@@ -17,6 +17,8 @@
 
 import java.util.List;
 
+import com.google.inject.Inject;
+
 /**
  * Rejects all public key management requests.
  *
@@ -25,6 +27,7 @@
  */
 public class NullKeyManager extends IPublicKeyManager {
 
+	@Inject
 	public NullKeyManager() {
 	}
 
diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
index 6bcc039..5a94c9a 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshDaemon.java
@@ -25,12 +25,12 @@
 import java.text.MessageFormat;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.apache.sshd.SshServer;
 import org.apache.sshd.common.io.IoServiceFactoryFactory;
 import org.apache.sshd.common.io.mina.MinaServiceFactoryFactory;
 import org.apache.sshd.common.io.nio2.Nio2ServiceFactoryFactory;
-import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
 import org.apache.sshd.common.util.SecurityUtils;
+import org.apache.sshd.server.SshServer;
+import org.apache.sshd.server.auth.CachingPublicKeyAuthenticator;
 import org.bouncycastle.openssl.PEMWriter;
 import org.eclipse.jgit.internal.JGitText;
 import org.slf4j.Logger;
@@ -98,8 +98,8 @@
 		hostKeyPairProvider.setFiles(new String [] { rsaKeyStore.getPath(), dsaKeyStore.getPath(), dsaKeyStore.getPath() });
 
 		// Client public key authenticator
-		CachingPublicKeyAuthenticator keyAuthenticator =
-				new CachingPublicKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit);
+		SshKeyAuthenticator keyAuthenticator =
+				new SshKeyAuthenticator(gitblit.getPublicKeyManager(), gitblit);
 
 		// Configure the preferred SSHD backend
 		String sshBackendStr = settings.getString(Keys.git.sshBackend,
@@ -125,8 +125,11 @@
 		sshd.setPort(addr.getPort());
 		sshd.setHost(addr.getHostName());
 		sshd.setKeyPairProvider(hostKeyPairProvider);
-		sshd.setPublickeyAuthenticator(keyAuthenticator);
+		sshd.setPublickeyAuthenticator(new CachingPublicKeyAuthenticator(keyAuthenticator));
 		sshd.setPasswordAuthenticator(new UsernamePasswordAuthenticator(gitblit));
+		if (settings.getBoolean(Keys.git.sshWithKrb5, false)) {
+			sshd.setGSSAuthenticator(new SshKrbAuthenticator(settings, gitblit));
+		}
 		sshd.setSessionFactory(new SshServerSessionFactory());
 		sshd.setFileSystemFactory(new DisabledFilesystemFactory());
 		sshd.setTcpipForwardingFilter(new NonForwardingFilter());
@@ -143,14 +146,22 @@
 	}
 
 	public String formatUrl(String gituser, String servername, String repository) {
-		if (sshd.getPort() == DEFAULT_PORT) {
+		IStoredSettings settings = gitblit.getSettings();
+
+		int port = sshd.getPort();
+		int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port);
+		String displayServername = settings.getString(Keys.git.sshAdvertisedHost, "");
+		if(displayServername.isEmpty()) {
+			displayServername = servername;
+		}
+		if (displayPort == DEFAULT_PORT) {
 			// standard port
-			return MessageFormat.format("ssh://{0}@{1}/{2}", gituser, servername,
+			return MessageFormat.format("ssh://{0}@{1}/{2}", gituser, displayServername,
 					repository);
 		} else {
 			// non-standard port
 			return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/{3}",
-					gituser, servername, sshd.getPort(), repository);
+					gituser, displayServername, displayPort, repository);
 		}
 	}
 
@@ -192,7 +203,7 @@
 			try {
 				((SshCommandFactory) sshd.getCommandFactory()).stop();
 				sshd.stop();
-			} catch (InterruptedException e) {
+			} catch (IOException e) {
 				log.error("SSH Daemon stop interrupted", e);
 			}
 		}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java b/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java
index a5d4c3d..af25251 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshDaemonClient.java
@@ -17,7 +17,7 @@
 
 import java.net.SocketAddress;
 
-import org.apache.sshd.common.Session.AttributeKey;
+import org.apache.sshd.common.session.Session.AttributeKey;
 
 import com.gitblit.models.UserModel;
 
diff --git a/src/main/java/com/gitblit/transport/ssh/SshKey.java b/src/main/java/com/gitblit/transport/ssh/SshKey.java
index 9c99d1a..9fd1005 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshKey.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshKey.java
@@ -22,7 +22,8 @@
 
 import org.apache.commons.codec.binary.Base64;
 import org.apache.sshd.common.SshException;
-import org.apache.sshd.common.util.Buffer;
+import org.apache.sshd.common.util.buffer.Buffer;
+import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 import org.eclipse.jgit.lib.Constants;
 
 import com.gitblit.Constants.AccessPermission;
@@ -72,7 +73,7 @@
 			}
 			final byte[] bin = Base64.decodeBase64(Constants.encodeASCII(parts[1]));
 			try {
-				publicKey = new Buffer(bin).getRawPublicKey();
+				publicKey = new ByteArrayBuffer(bin).getRawPublicKey();
 			} catch (SshException e) {
 				throw new RuntimeException(e);
 			}
@@ -145,7 +146,7 @@
 	public String getRawData() {
 		if (rawData == null && publicKey != null) {
 			// build the raw data manually from the public key
-			Buffer buf = new Buffer();
+			Buffer buf = new ByteArrayBuffer();
 
 			// 1: identify the algorithm
 			buf.putRawPublicKey(publicKey);
diff --git a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
similarity index 62%
rename from src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
rename to src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
index e804a0d..dc9d8a4 100644
--- a/src/main/java/com/gitblit/transport/ssh/CachingPublicKeyAuthenticator.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshKeyAuthenticator.java
@@ -16,15 +16,10 @@
 package com.gitblit.transport.ssh;
 
 import java.security.PublicKey;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
 
-import org.apache.sshd.common.Session;
-import org.apache.sshd.common.SessionListener;
-import org.apache.sshd.server.PublickeyAuthenticator;
+import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
 import org.apache.sshd.server.session.ServerSession;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,7 +32,7 @@
  * Authenticates an SSH session against a public key.
  *
  */
-public class CachingPublicKeyAuthenticator implements PublickeyAuthenticator, SessionListener {
+public class SshKeyAuthenticator implements PublickeyAuthenticator {
 
 	protected final Logger log = LoggerFactory.getLogger(getClass());
 
@@ -45,30 +40,13 @@
 
 	protected final IAuthenticationManager authManager;
 
-	private final Map<ServerSession, Map<PublicKey, Boolean>> cache = new ConcurrentHashMap<ServerSession, Map<PublicKey, Boolean>>();
-
-	public CachingPublicKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) {
+	public SshKeyAuthenticator(IPublicKeyManager keyManager, IAuthenticationManager authManager) {
 		this.keyManager = keyManager;
 		this.authManager = authManager;
 	}
 
 	@Override
-	public boolean authenticate(String username, PublicKey key, ServerSession session) {
-		Map<PublicKey, Boolean> map = cache.get(session);
-		if (map == null) {
-			map = new HashMap<PublicKey, Boolean>();
-			cache.put(session, map);
-			session.addListener(this);
-		}
-		if (map.containsKey(key)) {
-			return map.get(key);
-		}
-		boolean result = doAuthenticate(username, key, session);
-		map.put(key, result);
-		return result;
-	}
-
-	private boolean doAuthenticate(String username, PublicKey suppliedKey, ServerSession session) {
+	public boolean authenticate(String username, PublicKey suppliedKey, ServerSession session) {
 		SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
 		Preconditions.checkState(client.getUser() == null);
 		username = username.toLowerCase(Locale.US);
@@ -95,18 +73,5 @@
 
 		log.warn("could not authenticate {} for SSH using the supplied public key", username);
 		return false;
-	}
-
-	@Override
-	public void sessionCreated(Session session) {
-	}
-
-	@Override
-	public void sessionEvent(Session sesssion, Event event) {
-	}
-
-	@Override
-	public void sessionClosed(Session session) {
-		cache.remove(session);
 	}
 }
diff --git a/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java
new file mode 100644
index 0000000..b6d233c
--- /dev/null
+++ b/src/main/java/com/gitblit/transport/ssh/SshKrbAuthenticator.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 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.transport.ssh;
+
+import java.util.Locale;
+
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.IAuthenticationManager;
+import com.gitblit.models.UserModel;
+
+public class SshKrbAuthenticator extends GSSAuthenticator {
+
+	protected final Logger log = LoggerFactory.getLogger(getClass());
+	protected final IAuthenticationManager authManager;
+	protected final boolean stripDomain;
+
+
+	public SshKrbAuthenticator(IStoredSettings settings, IAuthenticationManager authManager) {
+		this.authManager = authManager;
+
+		String keytabString = settings.getString(Keys.git.sshKrb5Keytab, "");
+		if(! keytabString.isEmpty()) {
+			setKeytabFile(keytabString);
+		}
+
+		String servicePrincipalName = settings.getString(Keys.git.sshKrb5ServicePrincipalName, "");
+		if(! servicePrincipalName.isEmpty()) {
+			setServicePrincipalName(servicePrincipalName);
+		}
+
+		this.stripDomain = settings.getBoolean(Keys.git.sshKrb5StripDomain, false);
+	}
+
+	@Override
+	public boolean validateIdentity(ServerSession session, String identity) {
+		log.info("identify with kerberos {}", identity);
+		SshDaemonClient client = session.getAttribute(SshDaemonClient.KEY);
+		if (client.getUser() != null) {
+			log.info("{} has already authenticated!", identity);
+			return true;
+		}
+		String username = identity.toLowerCase(Locale.US);
+		if (stripDomain) {
+			int p = username.indexOf('@');
+			if (p > 0) {
+				username = username.substring(0, p);
+			}
+		}
+		UserModel user = authManager.authenticate(username);
+		if (user != null) {
+			client.setUser(user);
+			return true;
+		}
+		log.warn("could not authenticate {} for SSH", username);
+		return false;
+	}
+}
diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSession.java b/src/main/java/com/gitblit/transport/ssh/SshServerSession.java
index d12a6be..02504ec 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshServerSession.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshServerSession.java
@@ -19,10 +19,10 @@
 import org.apache.sshd.common.future.SshFutureListener;
 import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.server.ServerFactoryManager;
-import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.session.ServerSessionImpl;
 
 // Expose addition of close session listeners
-class SshServerSession extends ServerSession {
+class SshServerSession extends ServerSessionImpl {
 
 	SshServerSession(ServerFactoryManager server, IoSession ioSession) throws Exception {
 		super(server, ioSession);
diff --git a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
index 0c018f0..bc67cec 100644
--- a/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
+++ b/src/main/java/com/gitblit/transport/ssh/SshServerSessionFactory.java
@@ -67,6 +67,6 @@
 
 	@Override
 	protected AbstractSession doCreateSession(IoSession ioSession) throws Exception {
-		return new SshServerSession(server, ioSession);
+		return new SshServerSession(getServer(), ioSession);
 	}
 }
diff --git a/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java b/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java
index c4e69dc..e9e2d7e 100644
--- a/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java
+++ b/src/main/java/com/gitblit/transport/ssh/UsernamePasswordAuthenticator.java
@@ -17,7 +17,7 @@
 
 import java.util.Locale;
 
-import org.apache.sshd.server.PasswordAuthenticator;
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
 import org.apache.sshd.server.session.ServerSession;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,13 +51,13 @@
 		}
 
 		username = username.toLowerCase(Locale.US);
-		UserModel user = authManager.authenticate(username, password.toCharArray());
+		UserModel user = authManager.authenticate(username, password.toCharArray(), null);
 		if (user != null) {
 			client.setUser(user);
 			return true;
 		}
 
-		log.warn("could not authenticate {} for SSH using the supplied password", username);
+		log.warn("could not authenticate {} ({}) for SSH using the supplied password", username, client.getRemoteAddress());
 		return false;
 	}
 }
diff --git a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
index 852756a..ec6f729 100644
--- a/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
+++ b/src/main/java/com/gitblit/transport/ssh/WelcomeShell.java
@@ -200,13 +200,18 @@
 		}
 
 		private String formatUrl(String hostname, int port, String username) {
-			if (port == 22) {
+			int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port);
+			String displayHostname = settings.getString(Keys.git.sshAdvertisedHost, "");
+			if(displayHostname.isEmpty()) {
+				displayHostname = hostname;
+			}
+			if (displayPort == 22) {
 				// standard port
-				return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, hostname);
+				return MessageFormat.format("{0}@{1}/REPOSITORY.git", username, displayHostname);
 			} else {
 				// non-standard port
 				return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/REPOSITORY.git",
-						username, hostname, port);
+						username, displayHostname, displayPort);
 			}
 		}
 	}
diff --git a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java
index f18b99b..02764db 100644
--- a/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java
+++ b/src/main/java/com/gitblit/transport/ssh/commands/SshCommand.java
@@ -27,6 +27,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.utils.StringUtils;
@@ -73,15 +74,20 @@
 
 	protected String getRepositoryUrl(String repository) {
 		String username = getContext().getClient().getUsername();
-		String hostname = getHostname();
-		int port = getContext().getGitblit().getSettings().getInteger(Keys.git.sshPort, 0);
-		if (port == 22) {
+		IStoredSettings settings = getContext().getGitblit().getSettings();
+		String displayHostname = settings.getString(Keys.git.sshAdvertisedHost, "");
+		if(displayHostname.isEmpty()) {
+			displayHostname = getHostname();
+		}
+		int port = settings.getInteger(Keys.git.sshPort, 0);
+		int displayPort = settings.getInteger(Keys.git.sshAdvertisedPort, port);
+		if (displayPort == 22) {
 			// standard port
-			return MessageFormat.format("{0}@{1}/{2}.git", username, hostname, repository);
+			return MessageFormat.format("{0}@{1}/{2}.git", username, displayHostname, repository);
 		} else {
 			// non-standard port
 			return MessageFormat.format("ssh://{0}@{1}:{2,number,0}/{3}",
-					username, hostname, port, repository);
+					username, displayHostname, displayPort, repository);
 		}
 	}
 
diff --git a/src/main/java/com/gitblit/utils/ActivityUtils.java b/src/main/java/com/gitblit/utils/ActivityUtils.java
index ba5599a..ab1dad9 100644
--- a/src/main/java/com/gitblit/utils/ActivityUtils.java
+++ b/src/main/java/com/gitblit/utils/ActivityUtils.java
@@ -169,7 +169,7 @@
 		if (width <= 0) {
 			width = 50;
 		}
-		String emailHash = StringUtils.getMD5(email);
+		String emailHash = StringUtils.getMD5(email.toLowerCase());
 		String url = MessageFormat.format(
 				"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
 		return url;
@@ -188,7 +188,7 @@
 		if (width <= 0) {
 			width = 50;
 		}
-		String emailHash = StringUtils.getMD5(email);
+		String emailHash = StringUtils.getMD5(email.toLowerCase());
 		String url = MessageFormat.format(
 				"https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=mm", emailHash, width);
 		return url;
diff --git a/src/main/java/com/gitblit/utils/CompressionUtils.java b/src/main/java/com/gitblit/utils/CompressionUtils.java
index d4bfbb3..b06edd2 100644
--- a/src/main/java/com/gitblit/utils/CompressionUtils.java
+++ b/src/main/java/com/gitblit/utils/CompressionUtils.java
@@ -132,7 +132,7 @@
 		} catch (IOException e) {
 			error(e, repository, "{0} failed to zip files from commit {1}", commit.getName());
 		} finally {
-			tw.release();
+			tw.close();
 			rw.dispose();
 		}
 		return success;
@@ -291,7 +291,7 @@
 		} catch (IOException e) {
 			error(e, repository, "{0} failed to {1} stream files from commit {2}", algorithm, commit.getName());
 		} finally {
-			tw.release();
+			tw.close();
 			rw.dispose();
 		}
 		return success;
diff --git a/src/main/java/com/gitblit/utils/DiffUtils.java b/src/main/java/com/gitblit/utils/DiffUtils.java
index dd2a780..cdebec1 100644
--- a/src/main/java/com/gitblit/utils/DiffUtils.java
+++ b/src/main/java/com/gitblit/utils/DiffUtils.java
@@ -52,6 +52,27 @@
 	private static final Logger LOGGER = LoggerFactory.getLogger(DiffUtils.class);
 
 	/**
+	 * Callback interface for binary diffs. All the getDiff methods here take an optional handler;
+	 * if given and the {@link DiffOutputType} is {@link DiffOutputType#HTML HTML}, it is responsible
+	 * for displaying a binary diff.
+	 */
+	public interface BinaryDiffHandler {
+
+		/**
+		 * Renders a binary diff. The result must be valid HTML, it will be inserted into an HTML table cell.
+		 * May return {@code null} if the default behavior (which is typically just a textual note "Bnary
+		 * files differ") is desired.
+		 *
+		 * @param diffEntry
+		 *            current diff entry
+		 *
+		 * @return the rendered diff as HTML, or {@code null} if the default is desired.
+		 */
+		public String renderBinaryDiff(final DiffEntry diffEntry);
+
+	}
+
+	/**
 	 * Enumeration for the diff output types.
 	 */
 	public static enum DiffOutputType {
@@ -59,6 +80,40 @@
 
 		public static DiffOutputType forName(String name) {
 			for (DiffOutputType type : values()) {
+				if (type.name().equalsIgnoreCase(name)) {
+					return type;
+				}
+			}
+			return null;
+		}
+	}
+
+	/**
+	 * Enumeration for the diff comparator types.
+	 */
+	public static enum DiffComparator {
+		SHOW_WHITESPACE(RawTextComparator.DEFAULT),
+		IGNORE_WHITESPACE(RawTextComparator.WS_IGNORE_ALL),
+		IGNORE_LEADING(RawTextComparator.WS_IGNORE_LEADING),
+		IGNORE_TRAILING(RawTextComparator.WS_IGNORE_TRAILING),
+		IGNORE_CHANGES(RawTextComparator.WS_IGNORE_CHANGE);
+
+		public final RawTextComparator textComparator;
+
+		DiffComparator(RawTextComparator textComparator) {
+			this.textComparator = textComparator;
+		}
+
+		public DiffComparator getOpposite() {
+			return this == SHOW_WHITESPACE ? IGNORE_WHITESPACE : SHOW_WHITESPACE;
+		}
+
+		public String getTranslationKey() {
+			return "gb." + name().toLowerCase();
+		}
+
+		public static DiffComparator forName(String name) {
+			for (DiffComparator type : values()) {
 				if (type.name().equalsIgnoreCase(name)) {
 					return type;
 				}
@@ -172,12 +227,50 @@
 	 *
 	 * @param repository
 	 * @param commit
+	 * @param comparator
 	 * @param outputType
+	 * @param tabLength
 	 * @return the diff
 	 */
 	public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
-			DiffOutputType outputType) {
-		return getDiff(repository, null, commit, null, outputType);
+			DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+		return getDiff(repository, null, commit, null, comparator, outputType, tabLength);
+	}
+
+	/**
+	 * Returns the complete diff of the specified commit compared to its primary parent.
+	 *
+	 * @param repository
+	 * @param commit
+	 * @param comparator
+	 * @param outputType
+	 * @param handler
+	 *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+	 *            May be {@code null}, resulting in the default behavior.
+	 * @param tabLength
+	 * @return the diff
+	 */
+	public static DiffOutput getCommitDiff(Repository repository, RevCommit commit,
+			DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+		return getDiff(repository, null, commit, null, comparator, outputType, handler, tabLength);
+	}
+
+
+	/**
+	 * Returns the diff for the specified file or folder from the specified
+	 * commit compared to its primary parent.
+	 *
+	 * @param repository
+	 * @param commit
+	 * @param path
+	 * @param comparator
+	 * @param outputType
+	 * @param tabLength
+	 * @return the diff
+	 */
+	public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
+			DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+		return getDiff(repository, null, commit, path, comparator, outputType, tabLength);
 	}
 
 	/**
@@ -187,12 +280,17 @@
 	 * @param repository
 	 * @param commit
 	 * @param path
+	 * @param comparator
 	 * @param outputType
+	 * @param handler
+	 *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+	 *            May be {@code null}, resulting in the default behavior.
+	 * @param tabLength
 	 * @return the diff
 	 */
 	public static DiffOutput getDiff(Repository repository, RevCommit commit, String path,
-			DiffOutputType outputType) {
-		return getDiff(repository, null, commit, path, outputType);
+			DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+		return getDiff(repository, null, commit, path, comparator, outputType, handler, tabLength);
 	}
 
 	/**
@@ -201,12 +299,34 @@
 	 * @param repository
 	 * @param baseCommit
 	 * @param commit
+	 * @param comparator
 	 * @param outputType
+	 * @param tabLength
+	 *
 	 * @return the diff
 	 */
 	public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
-			DiffOutputType outputType) {
-		return getDiff(repository, baseCommit, commit, null, outputType);
+			DiffComparator comparator, DiffOutputType outputType, int tabLength) {
+		return getDiff(repository, baseCommit, commit, null, comparator, outputType, tabLength);
+	}
+
+	/**
+	 * Returns the complete diff between the two specified commits.
+	 *
+	 * @param repository
+	 * @param baseCommit
+	 * @param commit
+	 * @param comparator
+	 * @param outputType
+	 * @param handler
+	 *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+	 *            May be {@code null}, resulting in the default behavior.
+	 * @param tabLength
+	 * @return the diff
+	 */
+	public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
+			DiffComparator comparator, DiffOutputType outputType, BinaryDiffHandler handler, int tabLength) {
+		return getDiff(repository, baseCommit, commit, null, comparator, outputType, handler, tabLength);
 	}
 
 	/**
@@ -221,27 +341,54 @@
 	 *            if the path is specified, the diff is restricted to that file
 	 *            or folder. if unspecified, the diff is for the entire commit.
 	 * @param outputType
+	 * @param diffComparator
+	 * @param tabLength
 	 * @return the diff
 	 */
 	public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit,
-			String path, DiffOutputType outputType) {
+			String path, DiffComparator diffComparator, DiffOutputType outputType, int tabLength) {
+		return getDiff(repository, baseCommit, commit, path, diffComparator, outputType, null, tabLength);
+	}
+
+	/**
+	 * Returns the diff between two commits for the specified file.
+	 *
+	 * @param repository
+	 * @param baseCommit
+	 *            if base commit is null the diff is to the primary parent of
+	 *            the commit.
+	 * @param commit
+	 * @param path
+	 *            if the path is specified, the diff is restricted to that file
+	 *            or folder. if unspecified, the diff is for the entire commit.
+	 * @param comparator
+	 * @param outputType
+	 * @param handler
+	 *            to use for rendering binary diffs if {@code outputType} is {@link DiffOutputType#HTML HTML}.
+	 *            May be {@code null}, resulting in the default behavior.
+	 * @param tabLength
+	 * @return the diff
+	 */
+	public static DiffOutput getDiff(Repository repository, RevCommit baseCommit, RevCommit commit, String path,
+			DiffComparator comparator, DiffOutputType outputType, final BinaryDiffHandler handler, int tabLength) {
 		DiffStat stat = null;
 		String diff = null;
 		try {
-			final ByteArrayOutputStream os = new ByteArrayOutputStream();
-			RawTextComparator cmp = RawTextComparator.DEFAULT;
+			ByteArrayOutputStream os = null;
+
 			DiffFormatter df;
 			switch (outputType) {
 			case HTML:
-				df = new GitBlitDiffFormatter(os, commit.getName());
+				df = new GitBlitDiffFormatter(commit.getName(), path, handler, tabLength);
 				break;
 			case PLAIN:
 			default:
+				os = new ByteArrayOutputStream();
 				df = new DiffFormatter(os);
 				break;
 			}
 			df.setRepository(repository);
-			df.setDiffComparator(cmp);
+			df.setDiffComparator((comparator == null ? DiffComparator.SHOW_WHITESPACE : comparator).textComparator);
 			df.setDetectRenames(true);
 
 			RevTree commitTree = commit.getTree();
@@ -271,6 +418,7 @@
 			} else {
 				df.format(diffEntries);
 			}
+			df.flush();
 			if (df instanceof GitBlitDiffFormatter) {
 				// workaround for complex private methods in DiffFormatter
 				diff = ((GitBlitDiffFormatter) df).getHtml();
@@ -278,7 +426,6 @@
 			} else {
 				diff = os.toString();
 			}
-			df.flush();
 		} catch (Throwable t) {
 			LOGGER.error("failed to generate commit diff!", t);
 		}
diff --git a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
index 47ff143..86b7ca2 100644
--- a/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
+++ b/src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
@@ -1,236 +1,535 @@
-/*
- * Copyright 2011 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.utils;
-
-import static org.eclipse.jgit.lib.Constants.encode;
-import static org.eclipse.jgit.lib.Constants.encodeASCII;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.text.MessageFormat;
-
-import org.eclipse.jgit.diff.DiffEntry;
-import org.eclipse.jgit.diff.DiffFormatter;
-import org.eclipse.jgit.diff.RawText;
-import org.eclipse.jgit.util.RawParseUtils;
-
-import com.gitblit.models.PathModel.PathChangeModel;
-import com.gitblit.utils.DiffUtils.DiffStat;
-
-/**
- * Generates an html snippet of a diff in Gitblit's style, tracks changed paths,
- * and calculates diff stats.
- *
- * @author James Moger
- *
- */
-public class GitBlitDiffFormatter extends DiffFormatter {
-
-	private final OutputStream os;
-
-	private final DiffStat diffStat;
-
-	private PathChangeModel currentPath;
-
-	private int left, right;
-
-	public GitBlitDiffFormatter(OutputStream os, String commitId) {
-		super(os);
-		this.os = os;
-		this.diffStat = new DiffStat(commitId);
-	}
-
-	@Override
-	public void format(DiffEntry ent) throws IOException {
-		currentPath = diffStat.addPath(ent);
-		super.format(ent);
-	}
-
-	/**
-	 * Output a hunk header
-	 *
-	 * @param aStartLine
-	 *            within first source
-	 * @param aEndLine
-	 *            within first source
-	 * @param bStartLine
-	 *            within second source
-	 * @param bEndLine
-	 *            within second source
-	 * @throws IOException
-	 */
-	@Override
-	protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine)
-			throws IOException {
-		os.write("<tr><th>..</th><th>..</th><td class='hunk_header'>".getBytes());
-		os.write('@');
-		os.write('@');
-		writeRange('-', aStartLine + 1, aEndLine - aStartLine);
-		writeRange('+', bStartLine + 1, bEndLine - bStartLine);
-		os.write(' ');
-		os.write('@');
-		os.write('@');
-		os.write("</td></tr>\n".getBytes());
-		left = aStartLine + 1;
-		right = bStartLine + 1;
-	}
-
-	protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
-		os.write(' ');
-		os.write(prefix);
-		switch (cnt) {
-		case 0:
-			// If the range is empty, its beginning number must
-			// be the
-			// line just before the range, or 0 if the range is
-			// at the
-			// start of the file stream. Here, begin is always 1
-			// based,
-			// so an empty file would produce "0,0".
-			//
-			os.write(encodeASCII(begin - 1));
-			os.write(',');
-			os.write('0');
-			break;
-
-		case 1:
-			// If the range is exactly one line, produce only
-			// the number.
-			//
-			os.write(encodeASCII(begin));
-			break;
-
-		default:
-			os.write(encodeASCII(begin));
-			os.write(',');
-			os.write(encodeASCII(cnt));
-			break;
-		}
-	}
-
-	@Override
-	protected void writeLine(final char prefix, final RawText text, final int cur)
-			throws IOException {
-		// update entry diffstat
-		currentPath.update(prefix);
-
-		// output diff
-		os.write("<tr>".getBytes());
-		switch (prefix) {
-		case '+':
-			os.write(("<th></th><th>" + (right++) + "</th>").getBytes());
-			os.write("<td><div class=\"diff add2\">".getBytes());
-			break;
-		case '-':
-			os.write(("<th>" + (left++) + "</th><th></th>").getBytes());
-			os.write("<td><div class=\"diff remove2\">".getBytes());
-			break;
-		default:
-			os.write(("<th>" + (left++) + "</th><th>" + (right++) + "</th>").getBytes());
-			os.write("<td>".getBytes());
-			break;
-		}
-		os.write(prefix);
-		String line = text.getString(cur);
-		line = StringUtils.escapeForHtml(line, false);
-		os.write(encode(line));
-		switch (prefix) {
-		case '+':
-		case '-':
-			os.write("</div>".getBytes());
-			break;
-		default:
-			os.write("</td>".getBytes());
-		}
-		os.write("</tr>\n".getBytes());
-	}
-
-	/**
-	 * Workaround function for complex private methods in DiffFormatter. This
-	 * sets the html for the diff headers.
-	 *
-	 * @return
-	 */
-	public String getHtml() {
-		ByteArrayOutputStream bos = (ByteArrayOutputStream) os;
-		String html = RawParseUtils.decode(bos.toByteArray());
-		String[] lines = html.split("\n");
-		StringBuilder sb = new StringBuilder();
-		boolean inFile = false;
-		String oldnull = "a/dev/null";
-		for (String line : lines) {
-			if (line.startsWith("index")) {
-				// skip index lines
-			} else if (line.startsWith("new file")) {
-				// skip new file lines
-			} else if (line.startsWith("\\ No newline")) {
-				// skip no new line
-			} else if (line.startsWith("---") || line.startsWith("+++")) {
-				// skip --- +++ lines
-			} else if (line.startsWith("diff")) {
-				line = StringUtils.convertOctal(line);
-				if (line.indexOf(oldnull) > -1) {
-					// a is null, use b
-					line = line.substring(("diff --git " + oldnull).length()).trim();
-					// trim b/
-					line = line.substring(2).trim();
-				} else {
-					// use a
-					line = line.substring("diff --git ".length()).trim();
-					line = line.substring(line.startsWith("\"a/") ? 3 : 2);
-					line = line.substring(0, line.indexOf(" b/") > -1 ? line.indexOf(" b/") : line.indexOf("\"b/")).trim();
-				}
-
-				if (line.charAt(0) == '"') {
-					line = line.substring(1);
-				}
-				if (line.charAt(line.length() - 1) == '"') {
-					line = line.substring(0, line.length() - 1);
-				}
-				if (inFile) {
-					sb.append("</tbody></table></div>\n");
-					inFile = false;
-				}
-
-				sb.append(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"{0}\"><i class=\"icon-file\"></i> ", line)).append(line).append("</div></div>");
-				sb.append("<div class=\"diff\">");
-				sb.append("<table><tbody>");
-				inFile = true;
-			} else {
-				boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit");
-				if (gitLinkDiff) {
-					sb.append("<tr><th></th><th></th>");
-					if (line.charAt(0) == '+') {
-						sb.append("<td><div class=\"diff add2\">");
-					} else {
-						sb.append("<td><div class=\"diff remove2\">");
-					}
-				}
-				sb.append(line);
-				if (gitLinkDiff) {
-					sb.append("</div></td></tr>");
-				}
-			}
-		}
-		sb.append("</table></div>");
-		return sb.toString();
-	}
-
-	public DiffStat getDiffStat() {
-		return diffStat;
-	}
-}
+/*
+ * Copyright 2011 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.utils;
+
+import static org.eclipse.jgit.lib.Constants.encode;
+import static org.eclipse.jgit.lib.Constants.encodeASCII;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.wicket.Application;
+import org.apache.wicket.Localizer;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.ChangeType;
+import org.eclipse.jgit.diff.DiffFormatter;
+import org.eclipse.jgit.diff.RawText;
+import org.eclipse.jgit.util.RawParseUtils;
+
+import com.gitblit.models.PathModel.PathChangeModel;
+import com.gitblit.utils.DiffUtils.BinaryDiffHandler;
+import com.gitblit.utils.DiffUtils.DiffStat;
+import com.gitblit.wicket.GitBlitWebApp;
+
+/**
+ * Generates an html snippet of a diff in Gitblit's style, tracks changed paths, and calculates diff stats.
+ *
+ * @author James Moger
+ * @author Tom <tw201207@gmail.com>
+ *
+ */
+public class GitBlitDiffFormatter extends DiffFormatter {
+
+	/** Regex pattern identifying trailing whitespace. */
+	private static final Pattern trailingWhitespace = Pattern.compile("(\\s+?)\r?\n?$");
+
+	/**
+	 * gitblit.properties key for the per-file limit on the number of diff lines.
+	 */
+	private static final String DIFF_LIMIT_PER_FILE_KEY = "web.maxDiffLinesPerFile";
+
+	/**
+	 * gitblit.properties key for the global limit on the number of diff lines in a commitdiff.
+	 */
+	private static final String GLOBAL_DIFF_LIMIT_KEY = "web.maxDiffLines";
+
+	/**
+	 * Diffs with more lines are not shown in commitdiffs. (Similar to what GitHub does.) Can be reduced
+	 * (but not increased) through gitblit.properties key {@link #DIFF_LIMIT_PER_FILE_KEY}.
+	 */
+	private static final int DIFF_LIMIT_PER_FILE = 4000;
+
+	/**
+	 * Global diff limit. Commitdiffs with more lines are truncated. Can be reduced (but not increased)
+	 * through gitblit.properties key {@link #GLOBAL_DIFF_LIMIT_KEY}.
+	 */
+	private static final int GLOBAL_DIFF_LIMIT = 20000;
+
+	private static final boolean CONVERT_TABS = true;
+
+	private final DiffOutputStream os;
+
+	private final DiffStat diffStat;
+
+	private PathChangeModel currentPath;
+
+	private int left, right;
+
+	/**
+	 * If a single file diff in a commitdiff produces more than this number of lines, we don't display
+	 * the diff. First, it's too taxing on the browser: it'll spend an awful lot of time applying the
+	 * CSS rules (despite my having optimized them). And second, no human can read a diff with thousands
+	 * of lines and make sense of it.
+	 * <p>
+	 * Set to {@link #DIFF_LIMIT_PER_FILE} for commitdiffs, and to -1 (switches off the limit) for
+	 * single-file diffs.
+	 * </p>
+	 */
+	private final int maxDiffLinesPerFile;
+
+	/**
+	 * Global limit on the number of diff lines. Set to {@link #GLOBAL_DIFF_LIMIT} for commitdiffs, and
+	 * to -1 (switched off the limit) for single-file diffs.
+	 */
+	private final int globalDiffLimit;
+
+	/** Number of lines for the current file diff. Set to zero when a new DiffEntry is started. */
+	private int nofLinesCurrent;
+	/**
+	 * Position in the stream when we try to write the first line. Used to rewind when we detect that
+	 * the diff is too large.
+	 */
+	private int startCurrent;
+	/** Flag set to true when we rewind. Reset to false when we start a new DiffEntry. */
+	private boolean isOff;
+	/** The current diff entry. */
+	private DiffEntry entry;
+
+	// Global limit stuff.
+
+	/** Total number of lines written before the current diff entry. */
+	private int totalNofLinesPrevious;
+	/** Running total of the number of diff lines written. Updated until we exceed the global limit. */
+	private int totalNofLinesCurrent;
+	/** Stream position to reset to if we decided to truncate the commitdiff. */
+	private int truncateTo;
+	/** Whether we decided to truncate the commitdiff. */
+	private boolean truncated;
+	/** If {@link #truncated}, contains all entries skipped. */
+	private final List<DiffEntry> skipped = new ArrayList<DiffEntry>();
+
+	private int tabLength;
+
+	/**
+	 * A {@link ResettableByteArrayOutputStream} that intercept the "Binary files differ" message produced
+	 * by the super implementation. Unfortunately the super implementation has far too many things private;
+	 * otherwise we'd just have re-implemented {@link GitBlitDiffFormatter#format(DiffEntry) format(DiffEntry)}
+	 * completely without ever calling the super implementation.
+	 */
+	private static class DiffOutputStream extends ResettableByteArrayOutputStream {
+
+		private static final String BINARY_DIFFERENCE = "Binary files differ\n";
+
+		private GitBlitDiffFormatter formatter;
+		private BinaryDiffHandler binaryDiffHandler;
+
+		public void setFormatter(GitBlitDiffFormatter formatter, BinaryDiffHandler handler) {
+			this.formatter = formatter;
+			this.binaryDiffHandler = handler;
+		}
+
+		@Override
+		public void write(byte[] b, int offset, int length) {
+			if (binaryDiffHandler != null
+					&& RawParseUtils.decode(Arrays.copyOfRange(b, offset, offset + length)).contains(BINARY_DIFFERENCE))
+			{
+				String binaryDiff = binaryDiffHandler.renderBinaryDiff(formatter.entry);
+				if (binaryDiff != null) {
+					byte[] bb = ("<tr><td colspan='4' align='center'>" + binaryDiff + "</td></tr>").getBytes(StandardCharsets.UTF_8);
+					super.write(bb, 0, bb.length);
+					return;
+				}
+			}
+			super.write(b, offset, length);
+		}
+
+	}
+
+	public GitBlitDiffFormatter(String commitId, String path, BinaryDiffHandler handler, int tabLength) {
+		super(new DiffOutputStream());
+		this.os = (DiffOutputStream) getOutputStream();
+		this.os.setFormatter(this, handler);
+		this.diffStat = new DiffStat(commitId);
+		this.tabLength = tabLength;
+		// If we have a full commitdiff, install maxima to avoid generating a super-long diff listing that
+		// will only tax the browser too much.
+		maxDiffLinesPerFile = path != null ? -1 : getLimit(DIFF_LIMIT_PER_FILE_KEY, 500, DIFF_LIMIT_PER_FILE);
+		globalDiffLimit = path != null ? -1 : getLimit(GLOBAL_DIFF_LIMIT_KEY, 1000, GLOBAL_DIFF_LIMIT);
+	}
+
+	/**
+	 * Determines a limit to use for HTML diff output.
+	 *
+	 * @param key
+	 *            to use to read the value from the GitBlit settings, if available.
+	 * @param minimum
+	 *            minimum value to enforce
+	 * @param maximum
+	 *            maximum (and default) value to enforce
+	 * @return the limit
+	 */
+	private int getLimit(String key, int minimum, int maximum) {
+		if (Application.exists()) {
+			Application application = Application.get();
+			if (application instanceof GitBlitWebApp) {
+				GitBlitWebApp webApp = (GitBlitWebApp) application;
+				int configValue = webApp.settings().getInteger(key, maximum);
+				if (configValue < minimum) {
+					return minimum;
+				} else if (configValue < maximum) {
+					return configValue;
+				}
+			}
+		}
+		return maximum;
+	}
+
+	/**
+	 * Returns a localized message string, if there is a localization; otherwise the given default value.
+	 *
+	 * @param key
+	 *            message key for the message
+	 * @param defaultValue
+	 *            to use if no localization for the message can be found
+	 * @return the possibly localized message
+	 */
+	private String getMsg(String key, String defaultValue) {
+		if (Application.exists()) {
+			Localizer localizer = Application.get().getResourceSettings().getLocalizer();
+			if (localizer != null) {
+				// Use getStringIgnoreSettings because we don't want exceptions here if the key is missing!
+				return localizer.getStringIgnoreSettings(key, null, null, defaultValue);
+			}
+		}
+		return defaultValue;
+	}
+
+	@Override
+	public void format(DiffEntry ent) throws IOException {
+		currentPath = diffStat.addPath(ent);
+		nofLinesCurrent = 0;
+		isOff = false;
+		entry = ent;
+		if (!truncated) {
+			totalNofLinesPrevious = totalNofLinesCurrent;
+			if (globalDiffLimit > 0 && totalNofLinesPrevious > globalDiffLimit) {
+				truncated = true;
+				isOff = true;
+			}
+			truncateTo = os.size();
+		} else {
+			isOff = true;
+		}
+		if (truncated) {
+			skipped.add(ent);
+		} else {
+			// Produce a header here and now
+			String path;
+			String id;
+			if (ChangeType.DELETE.equals(ent.getChangeType())) {
+				path = ent.getOldPath();
+				id = ent.getOldId().name();
+			} else {
+				path = ent.getNewPath();
+				id = ent.getNewId().name();
+			}
+			StringBuilder sb = new StringBuilder(MessageFormat.format("<div class='header'><div class=\"diffHeader\" id=\"n{0}\"><i class=\"icon-file\"></i> ", id));
+			sb.append(StringUtils.escapeForHtml(path, false)).append("</div></div>");
+			sb.append("<div class=\"diff\"><table cellpadding='0'><tbody>\n");
+			os.write(sb.toString().getBytes());
+		}
+		// Keep formatting, but if off, don't produce anything anymore. We just keep on counting.
+		super.format(ent);
+		if (!truncated) {
+			// Close the table
+			os.write("</tbody></table></div>\n".getBytes());
+		}
+	}
+
+	@Override
+	public void flush() throws IOException {
+		if (truncated) {
+			os.resetTo(truncateTo);
+		}
+		super.flush();
+	}
+
+	/**
+	 * Rewind and issue a message that the diff is too large.
+	 */
+	private void reset() {
+		if (!isOff) {
+			os.resetTo(startCurrent);
+			writeFullWidthLine(getMsg("gb.diffFileDiffTooLarge", "Diff too large"));
+			totalNofLinesCurrent = totalNofLinesPrevious;
+			isOff = true;
+		}
+	}
+
+	/**
+	 * Writes an initial table row containing information about added/removed/renamed/copied files. In case
+	 * of a deletion, we also suppress generating the diff; it's not interesting. (All lines removed.)
+	 */
+	private void handleChange() {
+		// XXX Would be nice if we could generate blob links for the cases handled here. Alas, we lack the repo
+		// name, and cannot reliably determine it here. We could get the .git directory of a Repository, if we
+		// passed in the repo, and then take the name of the parent directory, but that'd fail for repos nested
+		// in GitBlit projects. And we don't know if the repo is inside a project or is a top-level repo.
+		//
+		// That's certainly solvable (just pass along more information), but would require a larger rewrite than
+		// I'm prepared to do now.
+		String message;
+		switch (entry.getChangeType()) {
+		case ADD:
+			message = getMsg("gb.diffNewFile", "New file");
+			break;
+		case DELETE:
+			message = getMsg("gb.diffDeletedFile", "File was deleted");
+			isOff = true;
+			break;
+		case RENAME:
+			message = MessageFormat.format(getMsg("gb.diffRenamedFile", "File was renamed from {0}"), entry.getOldPath());
+			break;
+		case COPY:
+			message = MessageFormat.format(getMsg("gb.diffCopiedFile", "File was copied from {0}"), entry.getOldPath());
+			break;
+		default:
+			return;
+		}
+		writeFullWidthLine(message);
+	}
+
+	/**
+	 * Output a hunk header
+	 *
+	 * @param aStartLine
+	 *            within first source
+	 * @param aEndLine
+	 *            within first source
+	 * @param bStartLine
+	 *            within second source
+	 * @param bEndLine
+	 *            within second source
+	 * @throws IOException
+	 */
+	@Override
+	protected void writeHunkHeader(int aStartLine, int aEndLine, int bStartLine, int bEndLine) throws IOException {
+		if (nofLinesCurrent++ == 0) {
+			handleChange();
+			startCurrent = os.size();
+		}
+		if (!isOff) {
+			totalNofLinesCurrent++;
+			if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) {
+				reset();
+			} else {
+				os.write("<tr><th class='diff-line' data-lineno='..'></th><th class='diff-line' data-lineno='..'></th><th class='diff-state'></th><td class='hunk_header'>"
+						.getBytes());
+				os.write('@');
+				os.write('@');
+				writeRange('-', aStartLine + 1, aEndLine - aStartLine);
+				writeRange('+', bStartLine + 1, bEndLine - bStartLine);
+				os.write(' ');
+				os.write('@');
+				os.write('@');
+				os.write("</td></tr>\n".getBytes());
+			}
+		}
+		left = aStartLine + 1;
+		right = bStartLine + 1;
+	}
+
+	protected void writeRange(final char prefix, final int begin, final int cnt) throws IOException {
+		os.write(' ');
+		os.write(prefix);
+		switch (cnt) {
+		case 0:
+			// If the range is empty, its beginning number must be the
+			// line just before the range, or 0 if the range is at the
+			// start of the file stream. Here, begin is always 1 based,
+			// so an empty file would produce "0,0".
+			//
+			os.write(encodeASCII(begin - 1));
+			os.write(',');
+			os.write('0');
+			break;
+
+		case 1:
+			// If the range is exactly one line, produce only the number.
+			//
+			os.write(encodeASCII(begin));
+			break;
+
+		default:
+			os.write(encodeASCII(begin));
+			os.write(',');
+			os.write(encodeASCII(cnt));
+			break;
+		}
+	}
+
+	/**
+	 * Writes a line spanning the full width of the code view, including the gutter.
+	 *
+	 * @param text
+	 *            to put on that line; will be HTML-escaped.
+	 */
+	private void writeFullWidthLine(String text) {
+		try {
+			os.write("<tr><td class='diff-cell' colspan='4'>".getBytes());
+			os.write(StringUtils.escapeForHtml(text, false).getBytes());
+			os.write("</td></tr>\n".getBytes());
+		} catch (IOException ex) {
+			// Cannot happen with a ByteArrayOutputStream
+		}
+	}
+
+	@Override
+	protected void writeLine(final char prefix, final RawText text, final int cur) throws IOException {
+		if (nofLinesCurrent++ == 0) {
+			handleChange();
+			startCurrent = os.size();
+		}
+		// update entry diffstat
+		currentPath.update(prefix);
+		if (isOff) {
+			return;
+		}
+		totalNofLinesCurrent++;
+		if (nofLinesCurrent > maxDiffLinesPerFile && maxDiffLinesPerFile > 0) {
+			reset();
+		} else {
+			// output diff
+			os.write("<tr>".getBytes());
+			switch (prefix) {
+			case '+':
+				os.write(("<th class='diff-line'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes());
+				os.write("<th class='diff-state diff-state-add'></th>".getBytes());
+				os.write("<td class='diff-cell add2'>".getBytes());
+				break;
+			case '-':
+				os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line'></th>").getBytes());
+				os.write("<th class='diff-state diff-state-sub'></th>".getBytes());
+				os.write("<td class='diff-cell remove2'>".getBytes());
+				break;
+			default:
+				os.write(("<th class='diff-line' data-lineno='" + (left++) + "'></th><th class='diff-line' data-lineno='" + (right++) + "'></th>").getBytes());
+				os.write("<th class='diff-state'></th>".getBytes());
+				os.write("<td class='diff-cell context2'>".getBytes());
+				break;
+			}
+			os.write(encode(codeLineToHtml(prefix, text.getString(cur))));
+			os.write("</td></tr>\n".getBytes());
+		}
+	}
+
+	/**
+	 * Convert the given code line to HTML.
+	 *
+	 * @param prefix
+	 *            the diff prefix (+/-) indicating whether the line was added or removed.
+	 * @param line
+	 *            the line to format as HTML
+	 * @return the HTML-formatted line, safe for inserting as is into HTML.
+	 */
+	private String codeLineToHtml(final char prefix, final String line) {
+		if ((prefix == '+' || prefix == '-')) {
+			// Highlight trailing whitespace on deleted/added lines.
+			Matcher matcher = trailingWhitespace.matcher(line);
+			if (matcher.find()) {
+				StringBuilder result = new StringBuilder(StringUtils.escapeForHtml(line.substring(0, matcher.start()), CONVERT_TABS, tabLength));
+				result.append("<span class='trailingws-").append(prefix == '+' ? "add" : "sub").append("'>");
+				result.append(StringUtils.escapeForHtml(matcher.group(1), false));
+				result.append("</span>");
+				return result.toString();
+			}
+		}
+		return StringUtils.escapeForHtml(line, CONVERT_TABS, tabLength);
+	}
+
+	/**
+	 * Workaround function for complex private methods in DiffFormatter. This sets the html for the diff headers.
+	 *
+	 * @return
+	 */
+	public String getHtml() {
+		String html = RawParseUtils.decode(os.toByteArray());
+		String[] lines = html.split("\n");
+		StringBuilder sb = new StringBuilder();
+		for (String line : lines) {
+			if (line.startsWith("index") || line.startsWith("similarity")
+					|| line.startsWith("rename from ") || line.startsWith("rename to ")) {
+				// skip index lines
+			} else if (line.startsWith("new file") || line.startsWith("deleted file")) {
+				// skip new file lines
+			} else if (line.startsWith("\\ No newline")) {
+				// skip no new line
+			} else if (line.startsWith("---") || line.startsWith("+++")) {
+				// skip --- +++ lines
+			} else if (line.startsWith("diff")) {
+				// skip diff lines
+			} else {
+				boolean gitLinkDiff = line.length() > 0 && line.substring(1).startsWith("Subproject commit");
+				if (gitLinkDiff) {
+					sb.append("<tr><th class='diff-line'></th><th class='diff-line'></th>");
+					if (line.charAt(0) == '+') {
+						sb.append("<th class='diff-state diff-state-add'></th><td class=\"diff-cell add2\">");
+					} else {
+						sb.append("<th class='diff-state diff-state-sub'></th><td class=\"diff-cell remove2\">");
+					}
+					line = StringUtils.escapeForHtml(line.substring(1), CONVERT_TABS, tabLength);
+				}
+				sb.append(line);
+				if (gitLinkDiff) {
+					sb.append("</td></tr>");
+				}
+				sb.append('\n');
+			}
+		}
+		if (truncated) {
+			sb.append(MessageFormat.format("<div class='header'><div class='diffHeader'>{0}</div></div>",
+					StringUtils.escapeForHtml(getMsg("gb.diffTruncated", "Diff truncated after the above file"), false)));
+			// List all files not shown. We can be sure we do have at least one path in skipped.
+			sb.append("<div class='diff'><table cellpadding='0'><tbody><tr><td class='diff-cell' colspan='4'>");
+			String deletedSuffix = StringUtils.escapeForHtml(getMsg("gb.diffDeletedFileSkipped", "(deleted)"), false);
+			boolean first = true;
+			for (DiffEntry entry : skipped) {
+				if (!first) {
+					sb.append('\n');
+				}
+				if (ChangeType.DELETE.equals(entry.getChangeType())) {
+					sb.append("<span id=\"n" + entry.getOldId().name() + "\">" + StringUtils.escapeForHtml(entry.getOldPath(), false) + ' ' + deletedSuffix + "</span>");
+				} else {
+					sb.append("<span id=\"n" + entry.getNewId().name() + "\">" + StringUtils.escapeForHtml(entry.getNewPath(), false) + "</span>");
+				}
+				first = false;
+			}
+			skipped.clear();
+			sb.append("</td></tr></tbody></table></div>");
+		}
+		return sb.toString();
+	}
+
+	public DiffStat getDiffStat() {
+		return diffStat;
+	}
+}
diff --git a/src/main/java/com/gitblit/utils/HtmlBuilder.java b/src/main/java/com/gitblit/utils/HtmlBuilder.java
new file mode 100644
index 0000000..6208ea8
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/HtmlBuilder.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2014 Tom <tw201207@gmail.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.utils;
+
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.parser.Tag;
+
+/**
+ * Simple helper class to hide some common setup needed to use JSoup as an HTML generator.
+ * A {@link HtmlBuilder} has a root element that can be manipulated in all the usual JSoup
+ * ways (but not added to some {@link Document}); to generate HTML for that root element,
+ * the builder's {@link #toString()} method can be used. (Or one can invoke toString()
+ * directly on the root element.) By default, a HTML builder does not pretty-print the HTML.
+ *
+ * @author Tom <tw201207@gmail.com>
+ */
+public class HtmlBuilder {
+
+	private final Document shell;
+	private final Element root;
+
+	/**
+	 * Creates a new HTML builder with a root element with the given {@code tagName}.
+	 *
+	 * @param tagName
+	 *            of the {@link Element} to set as the root element.
+	 */
+	public HtmlBuilder(String tagName) {
+		this(new Element(Tag.valueOf(tagName), ""));
+	}
+
+	/**
+	 * Creates a new HTML builder for the given element.
+	 *
+	 * @param element
+	 *            to set as the root element of this HTML builder.
+	 */
+	public HtmlBuilder(Element element) {
+		shell = Document.createShell("");
+		shell.outputSettings().prettyPrint(false);
+		shell.body().appendChild(element);
+		root = element;
+	}
+
+	/** @return the root element of this HTML builder */
+	public Element getRoot() {
+		return root;
+	}
+
+	/** @return the root element of this HTML builder */
+	public Element root() {
+		return root;
+	}
+
+	/** @return whether this HTML builder will pretty-print the HTML generated by {@link #toString()} */
+	public boolean prettyPrint() {
+		return shell.outputSettings().prettyPrint();
+	}
+
+	/**
+	 * Sets whether this HTML builder will produce pretty-printed HTML in its {@link #toString()} method.
+	 *
+	 * @param pretty
+	 *            whether to pretty-print
+	 * @return the HTML builder itself
+	 */
+	public HtmlBuilder prettyPrint(boolean pretty) {
+		shell.outputSettings().prettyPrint(pretty);
+		return this;
+	}
+
+	/** @return the HTML for the root element. */
+	@Override
+	public String toString() {
+		return root.toString();
+	}
+
+	/** @return the {@link Document} used as generation shell. */
+	protected Document getShell() {
+		return shell;
+	}
+}
diff --git a/src/main/java/com/gitblit/utils/JGitUtils.java b/src/main/java/com/gitblit/utils/JGitUtils.java
index da51ea9..c3d0207 100644
--- a/src/main/java/com/gitblit/utils/JGitUtils.java
+++ b/src/main/java/com/gitblit/utils/JGitUtils.java
@@ -90,6 +90,7 @@
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
 import com.gitblit.models.SubmoduleModel;
+import com.google.common.base.Strings;
 
 /**
  * Collection of static methods for retrieving information from a repository.
@@ -690,7 +691,10 @@
 		if (commit == null) {
 			return new Date(0);
 		}
-		return commit.getAuthorIdent().getWhen();
+		if (commit.getAuthorIdent() != null) {
+			return commit.getAuthorIdent().getWhen();
+		}
+		return getCommitDate(commit);
 	}
 
 	/**
@@ -773,7 +777,7 @@
 			}
 		} finally {
 			rw.dispose();
-			tw.release();
+			tw.close();
 		}
 		return content;
 	}
@@ -884,7 +888,64 @@
 		} catch (IOException e) {
 			error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
 		} finally {
-			tw.release();
+			tw.close();
+		}
+		Collections.sort(list);
+		return list;
+	}
+
+	/**
+	 * Returns the list of files in the specified folder at the specified
+	 * commit. If the repository does not exist or is empty, an empty list is
+	 * returned.
+	 *
+	 * This is modified version that implements path compression feature.
+	 *
+	 * @param repository
+	 * @param path
+	 *            if unspecified, root folder is assumed.
+	 * @param commit
+	 *            if null, HEAD is assumed.
+	 * @return list of files in specified path
+	 */
+	public static List<PathModel> getFilesInPath2(Repository repository, String path, RevCommit commit) {
+
+		List<PathModel> list = new ArrayList<PathModel>();
+		if (!hasCommits(repository)) {
+			return list;
+		}
+		if (commit == null) {
+			commit = getCommit(repository, null);
+		}
+		final TreeWalk tw = new TreeWalk(repository);
+		try {
+
+			tw.addTree(commit.getTree());
+			final boolean isPathEmpty = Strings.isNullOrEmpty(path);
+
+			if (!isPathEmpty) {
+				PathFilter f = PathFilter.create(path);
+				tw.setFilter(f);
+			}
+
+			tw.setRecursive(true);
+			List<String> paths = new ArrayList<>();
+
+			while (tw.next()) {
+					String child = isPathEmpty ? tw.getPathString()
+							: tw.getPathString().replaceFirst(String.format("%s/", path), "");
+					paths.add(child);
+			}
+
+			for(String p: PathUtils.compressPaths(paths)) {
+				String pathString = isPathEmpty ? p : String.format("%s/%s", path, p);
+				list.add(getPathModel(repository, pathString, path, commit));
+			}
+
+		} catch (IOException e) {
+			error(e, repository, "{0} failed to get files for commit {1}", commit.getName());
+		} finally {
+			tw.close();
 		}
 		Collections.sort(list);
 		return list;
@@ -936,7 +997,7 @@
 							.getRawMode(0), tw.getObjectId(0).getName(), commit.getId().getName(),
 							ChangeType.ADD));
 				}
-				tw.release();
+				tw.close();
 			} else {
 				RevCommit parent = rw.parseCommit(commit.getParent(0).getId());
 				DiffStatFormatter df = new DiffStatFormatter(commit.getName());
@@ -991,7 +1052,7 @@
 			RevCommit start = rw.parseCommit(startRange);
 			RevCommit end = rw.parseCommit(endRange);
 			list.addAll(getFilesInRange(repository, start, end));
-			rw.release();
+			rw.close();
 		} catch (Throwable t) {
 			error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
 		}
@@ -1089,7 +1150,7 @@
 		} catch (IOException e) {
 			error(e, repository, "{0} failed to get documents for commit {1}", commit.getName());
 		} finally {
-			tw.release();
+			tw.close();
 		}
 		Collections.sort(list);
 		return list;
@@ -1122,6 +1183,46 @@
 		return new PathModel(name, tw.getPathString(), size, tw.getFileMode(0).getBits(),
 				objectId.getName(), commit.getName());
 	}
+
+	/**
+	 * Returns a path model by path string
+	 *
+	 * @param repo
+	 * @param path
+	 * @param filter
+	 * @param commit
+	 * @return a path model of the specified object
+	 */
+	private static PathModel getPathModel(Repository repo, String path, String filter, RevCommit commit)
+			throws IOException {
+
+		long size = 0;
+		TreeWalk tw = TreeWalk.forPath(repo, path, commit.getTree());
+		String pathString = path;
+
+			if (!tw.isSubtree() && (tw.getFileMode(0) != FileMode.GITLINK)) {
+				size = tw.getObjectReader().getObjectSize(tw.getObjectId(0), Constants.OBJ_BLOB);
+				pathString = PathUtils.getLastPathComponent(pathString);
+
+			} else if (tw.isSubtree()) {
+
+				// do not display dirs that are behind in the path
+				if (!Strings.isNullOrEmpty(filter)) {
+					pathString = path.replaceFirst(filter + "/", "");
+				}
+
+				// remove the last slash from path in displayed link
+				if (pathString != null && pathString.charAt(pathString.length()-1) == '/') {
+					pathString = pathString.substring(0, pathString.length()-1);
+				}
+			}
+
+			return new PathModel(pathString, tw.getPathString(), size, tw.getFileMode(0).getBits(),
+					tw.getObjectId(0).getName(), commit.getName());
+
+
+	}
+
 
 	/**
 	 * Returns a permissions representation of the mode bits.
@@ -1946,7 +2047,7 @@
 			error(t, repository, "{0} can't find {1} in commit {2}", path, commit.name());
 		} finally {
 			rw.dispose();
-			tw.release();
+			tw.close();
 		}
 		return commitId;
 	}
@@ -2120,10 +2221,10 @@
 						success = false;
 					}
 				} finally {
-					revWalk.release();
+					revWalk.close();
 				}
 			} finally {
-				odi.release();
+				odi.close();
 			}
 		} catch (Throwable t) {
 			error(t, repository, "Failed to create orphan branch {1} in repository {0}", branchName);
@@ -2194,212 +2295,222 @@
 		}
 		return false;
 	}
-
-	/**
-	 * Returns true if the commit identified by commitId is an ancestor or the
-	 * the commit identified by tipId.
-	 *
-	 * @param repository
-	 * @param commitId
-	 * @param tipId
-	 * @return true if there is the commit is an ancestor of the tip
-	 */
-	public static boolean isMergedInto(Repository repository, String commitId, String tipId) {
-		try {
-			return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId));
-		} catch (Exception e) {
-			LOGGER.error("Failed to determine isMergedInto", e);
-		}
-		return false;
-	}
-
-	/**
-	 * Returns true if the commit identified by commitId is an ancestor or the
-	 * the commit identified by tipId.
-	 *
-	 * @param repository
-	 * @param commitId
-	 * @param tipId
-	 * @return true if there is the commit is an ancestor of the tip
-	 */
-	public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) {
-		// traverse the revlog looking for a commit chain between the endpoints
-		RevWalk rw = new RevWalk(repository);
-		try {
-			// must re-lookup RevCommits to workaround undocumented RevWalk bug
-			RevCommit tip = rw.lookupCommit(tipCommitId);
-			RevCommit commit = rw.lookupCommit(commitId);
-			return rw.isMergedInto(commit, tip);
-		} catch (Exception e) {
-			LOGGER.error("Failed to determine isMergedInto", e);
-		} finally {
-			rw.dispose();
-		}
-		return false;
-	}
-
-	/**
-	 * Returns the merge base of two commits or null if there is no common
-	 * ancestry.
-	 *
-	 * @param repository
-	 * @param commitIdA
-	 * @param commitIdB
-	 * @return the commit id of the merge base or null if there is no common base
-	 */
-	public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) {
-		RevWalk rw = new RevWalk(repository);
-		try {
-			RevCommit a = rw.lookupCommit(commitIdA);
-			RevCommit b = rw.lookupCommit(commitIdB);
-
-			rw.setRevFilter(RevFilter.MERGE_BASE);
-			rw.markStart(a);
-			rw.markStart(b);
-			RevCommit mergeBase = rw.next();
-			if (mergeBase == null) {
-				return null;
-			}
-			return mergeBase.getName();
-		} catch (Exception e) {
-			LOGGER.error("Failed to determine merge base", e);
-		} finally {
-			rw.dispose();
-		}
-		return null;
-	}
-
-	public static enum MergeStatus {
-		NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED;
-	}
-
-	/**
-	 * Determines if we can cleanly merge one branch into another.  Returns true
-	 * if we can merge without conflict, otherwise returns false.
-	 *
-	 * @param repository
-	 * @param src
-	 * @param toBranch
-	 * @return true if we can merge without conflict
-	 */
-	public static MergeStatus canMerge(Repository repository, String src, String toBranch) {
-		RevWalk revWalk = null;
-		try {
-			revWalk = new RevWalk(repository);
-			RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
-			RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
-			if (revWalk.isMergedInto(srcTip, branchTip)) {
-				// already merged
-				return MergeStatus.ALREADY_MERGED;
-			} else if (revWalk.isMergedInto(branchTip, srcTip)) {
-				// fast-forward
-				return MergeStatus.MERGEABLE;
-			}
-			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
-			boolean canMerge = merger.merge(branchTip, srcTip);
-			if (canMerge) {
-				return MergeStatus.MERGEABLE;
-			}
-		} catch (IOException e) {
-			LOGGER.error("Failed to determine canMerge", e);
+
+	/**
+	 * Returns true if the commit identified by commitId is an ancestor or the
+	 * the commit identified by tipId.
+	 *
+	 * @param repository
+	 * @param commitId
+	 * @param tipId
+	 * @return true if there is the commit is an ancestor of the tip
+	 */
+	public static boolean isMergedInto(Repository repository, String commitId, String tipId) {
+		try {
+			return isMergedInto(repository, repository.resolve(commitId), repository.resolve(tipId));
+		} catch (Exception e) {
+			LOGGER.error("Failed to determine isMergedInto", e);
+		}
+		return false;
+	}
+
+	/**
+	 * Returns true if the commit identified by commitId is an ancestor or the
+	 * the commit identified by tipId.
+	 *
+	 * @param repository
+	 * @param commitId
+	 * @param tipId
+	 * @return true if there is the commit is an ancestor of the tip
+	 */
+	public static boolean isMergedInto(Repository repository, ObjectId commitId, ObjectId tipCommitId) {
+		// traverse the revlog looking for a commit chain between the endpoints
+		RevWalk rw = new RevWalk(repository);
+		try {
+			// must re-lookup RevCommits to workaround undocumented RevWalk bug
+			RevCommit tip = rw.lookupCommit(tipCommitId);
+			RevCommit commit = rw.lookupCommit(commitId);
+			return rw.isMergedInto(commit, tip);
+		} catch (Exception e) {
+			LOGGER.error("Failed to determine isMergedInto", e);
 		} finally {
-			if (revWalk != null) {
-				revWalk.release();
-			}
-		}
-		return MergeStatus.NOT_MERGEABLE;
-	}
-
-
-	public static class MergeResult {
-		public final MergeStatus status;
-		public final String sha;
-
-		MergeResult(MergeStatus status, String sha) {
-			this.status = status;
-			this.sha = sha;
-		}
-	}
-
-	/**
-	 * Tries to merge a commit into a branch.  If there are conflicts, the merge
-	 * will fail.
-	 *
-	 * @param repository
-	 * @param src
-	 * @param toBranch
-	 * @param committer
-	 * @param message
-	 * @return the merge result
-	 */
-	public static MergeResult merge(Repository repository, String src, String toBranch,
-			PersonIdent committer, String message) {
-
-		if (!toBranch.startsWith(Constants.R_REFS)) {
-			// branch ref doesn't start with ref, assume this is a branch head
-			toBranch = Constants.R_HEADS + toBranch;
-		}
-
-		RevWalk revWalk = null;
-		try {
-			revWalk = new RevWalk(repository);
-			RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
-			RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
-			if (revWalk.isMergedInto(srcTip, branchTip)) {
-				// already merged
-				return new MergeResult(MergeStatus.ALREADY_MERGED, null);
-			}
-			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
-			boolean merged = merger.merge(branchTip, srcTip);
-			if (merged) {
-				// create a merge commit and a reference to track the merge commit
-				ObjectId treeId = merger.getResultTreeId();
-				ObjectInserter odi = repository.newObjectInserter();
-				try {
-					// Create a commit object
-					CommitBuilder commitBuilder = new CommitBuilder();
-					commitBuilder.setCommitter(committer);
-					commitBuilder.setAuthor(committer);
-					commitBuilder.setEncoding(Constants.CHARSET);
-					if (StringUtils.isEmpty(message)) {
-						message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());
-					}
-					commitBuilder.setMessage(message);
-					commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());
-					commitBuilder.setTreeId(treeId);
-
-					// Insert the merge commit into the repository
-					ObjectId mergeCommitId = odi.insert(commitBuilder);
-					odi.flush();
-
-					// set the merge ref to the merge commit
-					RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);
-					RefUpdate mergeRefUpdate = repository.updateRef(toBranch);
-					mergeRefUpdate.setNewObjectId(mergeCommitId);
-					mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);
-					RefUpdate.Result rc = mergeRefUpdate.update();
-					switch (rc) {
-					case FAST_FORWARD:
-						// successful, clean merge
+			rw.dispose();
+		}
+		return false;
+	}
+
+	/**
+	 * Returns the merge base of two commits or null if there is no common
+	 * ancestry.
+	 *
+	 * @param repository
+	 * @param commitIdA
+	 * @param commitIdB
+	 * @return the commit id of the merge base or null if there is no common base
+	 */
+	public static String getMergeBase(Repository repository, ObjectId commitIdA, ObjectId commitIdB) {
+		RevWalk rw = new RevWalk(repository);
+		try {
+			RevCommit a = rw.lookupCommit(commitIdA);
+			RevCommit b = rw.lookupCommit(commitIdB);
+
+			rw.setRevFilter(RevFilter.MERGE_BASE);
+			rw.markStart(a);
+			rw.markStart(b);
+			RevCommit mergeBase = rw.next();
+			if (mergeBase == null) {
+				return null;
+			}
+			return mergeBase.getName();
+		} catch (Exception e) {
+			LOGGER.error("Failed to determine merge base", e);
+		} finally {
+			rw.dispose();
+		}
+		return null;
+	}
+
+	public static enum MergeStatus {
+		MISSING_INTEGRATION_BRANCH, MISSING_SRC_BRANCH, NOT_MERGEABLE, FAILED, ALREADY_MERGED, MERGEABLE, MERGED;
+	}
+
+	/**
+	 * Determines if we can cleanly merge one branch into another.  Returns true
+	 * if we can merge without conflict, otherwise returns false.
+	 *
+	 * @param repository
+	 * @param src
+	 * @param toBranch
+	 * @return true if we can merge without conflict
+	 */
+	public static MergeStatus canMerge(Repository repository, String src, String toBranch) {
+		RevWalk revWalk = null;
+		try {
+			revWalk = new RevWalk(repository);
+			ObjectId branchId = repository.resolve(toBranch);
+			if (branchId == null) {
+				return MergeStatus.MISSING_INTEGRATION_BRANCH;
+			}
+			ObjectId srcId = repository.resolve(src);
+			if (srcId == null) {
+				return MergeStatus.MISSING_SRC_BRANCH;
+			}
+			RevCommit branchTip = revWalk.lookupCommit(branchId);
+			RevCommit srcTip = revWalk.lookupCommit(srcId);
+			if (revWalk.isMergedInto(srcTip, branchTip)) {
+				// already merged
+				return MergeStatus.ALREADY_MERGED;
+			} else if (revWalk.isMergedInto(branchTip, srcTip)) {
+				// fast-forward
+				return MergeStatus.MERGEABLE;
+			}
+			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+			boolean canMerge = merger.merge(branchTip, srcTip);
+			if (canMerge) {
+				return MergeStatus.MERGEABLE;
+			}
+		} catch (NullPointerException e) {
+			LOGGER.error("Failed to determine canMerge", e);
+		} catch (IOException e) {
+			LOGGER.error("Failed to determine canMerge", e);
+		} finally {
+			if (revWalk != null) {
+				revWalk.close();
+			}
+		}
+		return MergeStatus.NOT_MERGEABLE;
+	}
+
+
+	public static class MergeResult {
+		public final MergeStatus status;
+		public final String sha;
+
+		MergeResult(MergeStatus status, String sha) {
+			this.status = status;
+			this.sha = sha;
+		}
+	}
+
+	/**
+	 * Tries to merge a commit into a branch.  If there are conflicts, the merge
+	 * will fail.
+	 *
+	 * @param repository
+	 * @param src
+	 * @param toBranch
+	 * @param committer
+	 * @param message
+	 * @return the merge result
+	 */
+	public static MergeResult merge(Repository repository, String src, String toBranch,
+			PersonIdent committer, String message) {
+
+		if (!toBranch.startsWith(Constants.R_REFS)) {
+			// branch ref doesn't start with ref, assume this is a branch head
+			toBranch = Constants.R_HEADS + toBranch;
+		}
+
+		RevWalk revWalk = null;
+		try {
+			revWalk = new RevWalk(repository);
+			RevCommit branchTip = revWalk.lookupCommit(repository.resolve(toBranch));
+			RevCommit srcTip = revWalk.lookupCommit(repository.resolve(src));
+			if (revWalk.isMergedInto(srcTip, branchTip)) {
+				// already merged
+				return new MergeResult(MergeStatus.ALREADY_MERGED, null);
+			}
+			RecursiveMerger merger = (RecursiveMerger) MergeStrategy.RECURSIVE.newMerger(repository, true);
+			boolean merged = merger.merge(branchTip, srcTip);
+			if (merged) {
+				// create a merge commit and a reference to track the merge commit
+				ObjectId treeId = merger.getResultTreeId();
+				ObjectInserter odi = repository.newObjectInserter();
+				try {
+					// Create a commit object
+					CommitBuilder commitBuilder = new CommitBuilder();
+					commitBuilder.setCommitter(committer);
+					commitBuilder.setAuthor(committer);
+					commitBuilder.setEncoding(Constants.CHARSET);
+					if (StringUtils.isEmpty(message)) {
+						message = MessageFormat.format("merge {0} into {1}", srcTip.getName(), branchTip.getName());
+					}
+					commitBuilder.setMessage(message);
+					commitBuilder.setParentIds(branchTip.getId(), srcTip.getId());
+					commitBuilder.setTreeId(treeId);
+
+					// Insert the merge commit into the repository
+					ObjectId mergeCommitId = odi.insert(commitBuilder);
+					odi.flush();
+
+					// set the merge ref to the merge commit
+					RevCommit mergeCommit = revWalk.parseCommit(mergeCommitId);
+					RefUpdate mergeRefUpdate = repository.updateRef(toBranch);
+					mergeRefUpdate.setNewObjectId(mergeCommitId);
+					mergeRefUpdate.setRefLogMessage("commit: " + mergeCommit.getShortMessage(), false);
+					RefUpdate.Result rc = mergeRefUpdate.update();
+					switch (rc) {
+					case FAST_FORWARD:
+						// successful, clean merge
 						break;
-					default:
-						throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",
-								rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));
-					}
-
-					// return the merge commit id
-					return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());
-				} finally {
-					odi.release();
-				}
-			}
-		} catch (IOException e) {
-			LOGGER.error("Failed to merge", e);
+					default:
+						throw new GitBlitException(MessageFormat.format("Unexpected result \"{0}\" when merging commit {1} into {2} in {3}",
+								rc.name(), srcTip.getName(), branchTip.getName(), repository.getDirectory()));
+					}
+
+					// return the merge commit id
+					return new MergeResult(MergeStatus.MERGED, mergeCommitId.getName());
+				} finally {
+					odi.close();
+				}
+			}
+		} catch (IOException e) {
+			LOGGER.error("Failed to merge", e);
 		} finally {
-			if (revWalk != null) {
-				revWalk.release();
-			}
-		}
-		return new MergeResult(MergeStatus.FAILED, null);
-	}
+			if (revWalk != null) {
+				revWalk.close();
+			}
+		}
+		return new MergeResult(MergeStatus.FAILED, null);
+	}
 }
diff --git a/src/main/java/com/gitblit/utils/JSoupXssFilter.java b/src/main/java/com/gitblit/utils/JSoupXssFilter.java
index 5ab7953..aec2241 100644
--- a/src/main/java/com/gitblit/utils/JSoupXssFilter.java
+++ b/src/main/java/com/gitblit/utils/JSoupXssFilter.java
@@ -20,18 +20,23 @@
 import org.jsoup.safety.Cleaner;
 import org.jsoup.safety.Whitelist;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
 /**
  * Implementation of an XSS filter based on JSoup.
  *
  * @author James Moger
  *
  */
+@Singleton
 public class JSoupXssFilter implements XssFilter {
 
 	 private final Cleaner none;
 
 	 private final Cleaner relaxed;
 
+	 @Inject
 	 public JSoupXssFilter() {
 		 none = new Cleaner(Whitelist.none());
 		 relaxed = new Cleaner(getRelaxedWhiteList());
diff --git a/src/main/java/com/gitblit/utils/JsonUtils.java b/src/main/java/com/gitblit/utils/JsonUtils.java
index be7148c..f389776 100644
--- a/src/main/java/com/gitblit/utils/JsonUtils.java
+++ b/src/main/java/com/gitblit/utils/JsonUtils.java
@@ -46,6 +46,7 @@
 import com.google.gson.JsonDeserializationContext;
 import com.google.gson.JsonDeserializer;
 import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
 import com.google.gson.JsonPrimitive;
 import com.google.gson.JsonSerializationContext;
 import com.google.gson.JsonSerializer;
@@ -79,23 +80,29 @@
 
 	/**
 	 * Convert a json string to an object of the specified type.
-	 *
+	 * 
 	 * @param json
 	 * @param clazz
-	 * @return an object
+	 * @return the deserialized object
+	 * @throws JsonParseException
+	 * @throws JsonSyntaxException
 	 */
-	public static <X> X fromJsonString(String json, Class<X> clazz) {
+	public static <X> X fromJsonString(String json, Class<X> clazz) throws JsonParseException,
+			JsonSyntaxException {
 		return gson().fromJson(json, clazz);
 	}
 
 	/**
 	 * Convert a json string to an object of the specified type.
-	 *
+	 * 
 	 * @param json
-	 * @param clazz
-	 * @return an object
+	 * @param type
+	 * @return the deserialized object
+	 * @throws JsonParseException
+	 * @throws JsonSyntaxException
 	 */
-	public static <X> X fromJsonString(String json, Type type) {
+	public static <X> X fromJsonString(String json, Type type) throws JsonParseException,
+			JsonSyntaxException {
 		return gson().fromJson(json, type);
 	}
 
diff --git a/src/main/java/com/gitblit/utils/MarkdownUtils.java b/src/main/java/com/gitblit/utils/MarkdownUtils.java
index 2ebfdb2..e0c9dd4 100644
--- a/src/main/java/com/gitblit/utils/MarkdownUtils.java
+++ b/src/main/java/com/gitblit/utils/MarkdownUtils.java
@@ -16,6 +16,7 @@
 package com.gitblit.utils;
 
 import static org.pegdown.Extensions.ALL;
+import static org.pegdown.Extensions.ANCHORLINKS;
 import static org.pegdown.Extensions.SMARTYPANTS;
 
 import java.io.IOException;
@@ -76,7 +77,7 @@
 	 */
 	public static String transformMarkdown(String markdown, LinkRenderer linkRenderer) {
 		try {
-			PegDownProcessor pd = new PegDownProcessor(ALL & ~SMARTYPANTS);
+			PegDownProcessor pd = new PegDownProcessor(ALL & ~SMARTYPANTS & ~ANCHORLINKS);
 			RootNode astRoot = pd.parseMarkdown(markdown.toCharArray());
 			return new WorkaroundHtmlSerializer(linkRenderer == null ? new LinkRenderer() : linkRenderer).toHtml(astRoot);
 		} catch (ParsingTimeoutException e) {
diff --git a/src/main/java/com/gitblit/utils/MetricUtils.java b/src/main/java/com/gitblit/utils/MetricUtils.java
index 4703102..62427e6 100644
--- a/src/main/java/com/gitblit/utils/MetricUtils.java
+++ b/src/main/java/com/gitblit/utils/MetricUtils.java
@@ -136,7 +136,7 @@
 
 				Iterable<RevCommit> revlog = revWalk;
 				for (RevCommit rev : revlog) {
-					Date d = JGitUtils.getCommitDate(rev);
+					Date d = JGitUtils.getAuthorDate(rev);
 					String p = df.format(d);
 					if (!metricMap.containsKey(p)) {
 						metricMap.put(p, new Metric(p));
diff --git a/src/main/java/com/gitblit/utils/PathUtils.java b/src/main/java/com/gitblit/utils/PathUtils.java
new file mode 100644
index 0000000..a3c7d8d
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/PathUtils.java
@@ -0,0 +1,92 @@
+package com.gitblit.utils;
+
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+
+import java.util.*;
+
+/**
+ *  Utils for handling path strings
+ *
+ */
+public class PathUtils {
+
+    private PathUtils() {}
+
+    /**
+     *  Compress paths containing no files
+     *
+     * @param paths lines from `git ls-tree -r --name-only ${branch}`
+     * @return compressed paths
+     */
+    public static List<String> compressPaths(final Iterable<String> paths)  {
+
+        ArrayList<String> pathList = new ArrayList<>();
+        Map<String, List<String[]>> folderRoots = new LinkedHashMap<>();
+
+        for (String s: paths) {
+            String[] components = s.split("/");
+
+            // File in current directory
+            if (components.length == 1) {
+                pathList.add(components[0]);
+
+                // Directory path
+            } else {
+                List<String[]> rootedPaths = folderRoots.get(components[0]);
+                if (rootedPaths == null) {
+                    rootedPaths = new ArrayList<>();
+                }
+                rootedPaths.add(components);
+                folderRoots.put(components[0], rootedPaths);
+            }
+        }
+
+        for (String folder: folderRoots.keySet()) {
+            List<String[]> matchingPaths = folderRoots.get(folder);
+
+            if (matchingPaths.size() == 1) {
+                pathList.add(toStringPath(matchingPaths.get(0)));
+            } else {
+                pathList.add(longestCommonSequence(matchingPaths));
+            }
+        }
+        return pathList;
+    }
+
+    /**
+     *  Get last path component
+     *
+     *
+     * @param path string path separated by slashes
+     * @return rightmost entry
+     */
+    public static String getLastPathComponent(final String path) {
+        return Iterables.getLast(Splitter.on("/").omitEmptyStrings().split(path), path);
+    }
+
+    private static String toStringPath(final String[] pathComponents) {
+        List<String> tmp = Arrays.asList(pathComponents);
+        return Joiner.on('/').join(tmp.subList(0,tmp.size()-1)) + '/';
+    }
+
+
+    private static String longestCommonSequence(final List<String[]> paths) {
+
+        StringBuilder path = new StringBuilder();
+
+        for (int i = 0; i < paths.get(0).length; i++) {
+            String current = paths.get(0)[i];
+            for (int j = 1; j < paths.size(); j++) {
+                if (!current.equals(paths.get(j)[i])) {
+                    return path.toString();
+                }
+            }
+            path.append(current);
+            path.append('/');
+        }
+        return path.toString();
+    }
+
+}
diff --git a/src/main/java/com/gitblit/utils/RefLogUtils.java b/src/main/java/com/gitblit/utils/RefLogUtils.java
index f08c99e..355c120 100644
--- a/src/main/java/com/gitblit/utils/RefLogUtils.java
+++ b/src/main/java/com/gitblit/utils/RefLogUtils.java
@@ -294,10 +294,10 @@
 								rc));
 					}
 				} finally {
-					revWalk.release();
+					revWalk.close();
 				}
 			} finally {
-				odi.release();
+				odi.close();
 			}
 		} catch (Throwable t) {
 			error(t, repository, "Failed to commit reflog entry to {0}");
@@ -395,12 +395,12 @@
 			}
 
 			// release the treewalk
-			treeWalk.release();
+			treeWalk.close();
 
 			// finish temporary in-core index used for this commit
 			dcBuilder.finish();
 		} finally {
-			inserter.release();
+			inserter.close();
 		}
 		return inCoreIndex;
 	}
diff --git a/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java
new file mode 100644
index 0000000..7df0693
--- /dev/null
+++ b/src/main/java/com/gitblit/utils/ResettableByteArrayOutputStream.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 Tom <tw201207@gmail.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.utils;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * A {@link ByteArrayOutputStream} that can be reset to a specified position.
+ *
+ * @author Tom <tw201207@gmail.com>
+ */
+public class ResettableByteArrayOutputStream extends ByteArrayOutputStream {
+
+	/**
+	 * Reset the stream to the given position. If {@code mark} is <= 0, see {@link #reset()}.
+	 * A no-op if the stream contains less than {@code mark} bytes. Otherwise, resets the
+	 * current writing position to {@code mark}. Previously allocated buffer space will be
+	 * reused in subsequent writes.
+	 *
+	 * @param mark
+	 *            to set the current writing position to.
+	 */
+	public synchronized void resetTo(int mark) {
+		if (mark <= 0) {
+			reset();
+		} else if (mark < count) {
+			count = mark;
+		}
+	}
+
+}
diff --git a/src/main/java/com/gitblit/utils/StringUtils.java b/src/main/java/com/gitblit/utils/StringUtils.java
index 087de54..643c52c 100644
--- a/src/main/java/com/gitblit/utils/StringUtils.java
+++ b/src/main/java/com/gitblit/utils/StringUtils.java
@@ -79,6 +79,19 @@
 	 * @return plain text escaped for html
 	 */
 	public static String escapeForHtml(String inStr, boolean changeSpace) {
+		return escapeForHtml(inStr, changeSpace, 4);
+	}
+
+	/**
+	 * Prepare text for html presentation. Replace sensitive characters with
+	 * html entities.
+	 *
+	 * @param inStr
+	 * @param changeSpace
+	 * @param tabLength
+	 * @return plain text escaped for html
+	 */
+	public static String escapeForHtml(String inStr, boolean changeSpace, int tabLength) {
 		StringBuilder retStr = new StringBuilder();
 		int i = 0;
 		while (i < inStr.length()) {
@@ -93,7 +106,9 @@
 			} else if (changeSpace && inStr.charAt(i) == ' ') {
 				retStr.append("&nbsp;");
 			} else if (changeSpace && inStr.charAt(i) == '\t') {
-				retStr.append(" &nbsp; &nbsp;");
+				for (int j = 0; j < tabLength; j++) {
+					retStr.append("&nbsp;");
+				}
 			} else {
 				retStr.append(inStr.charAt(i));
 			}
diff --git a/src/main/java/com/gitblit/utils/X509Utils.java b/src/main/java/com/gitblit/utils/X509Utils.java
index f0c1b9d..a2650be 100644
--- a/src/main/java/com/gitblit/utils/X509Utils.java
+++ b/src/main/java/com/gitblit/utils/X509Utils.java
@@ -61,6 +61,7 @@
 import java.util.zip.ZipOutputStream;
 
 import javax.crypto.Cipher;
+import javax.naming.ldap.LdapName;
 
 import org.bouncycastle.asn1.ASN1ObjectIdentifier;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -80,7 +81,10 @@
 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
 import org.bouncycastle.jce.PrincipalUtil;
 import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
+import org.bouncycastle.openssl.PEMEncryptor;
 import org.bouncycastle.openssl.PEMWriter;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
+import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
 import org.bouncycastle.operator.ContentSigner;
 import org.bouncycastle.operator.OperatorCreationException;
 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
@@ -883,8 +887,11 @@
 	        if (pemFile.exists()) {
 	        	pemFile.delete();
 	        }
-	        PEMWriter pemWriter = new PEMWriter(new FileWriter(pemFile));
-	        pemWriter.writeObject(pair.getPrivate(), "DES-EDE3-CBC", clientMetadata.password.toCharArray(), new SecureRandom());
+	        JcePEMEncryptorBuilder builder = new JcePEMEncryptorBuilder("DES-EDE3-CBC");
+	        builder.setSecureRandom(new SecureRandom());
+	        PEMEncryptor pemEncryptor = builder.build(clientMetadata.password.toCharArray());
+	        JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemFile));
+	        pemWriter.writeObject(pair.getPrivate(), pemEncryptor);
 	        pemWriter.writeObject(userCert);
 	        pemWriter.writeObject(caCert);
 	        pemWriter.flush();
@@ -1111,17 +1118,18 @@
 	}
 
 	public static X509Metadata getMetadata(X509Certificate cert) {
-		// manually split DN into OID components
-		// this is instead of parsing with LdapName which:
-		// (1) I don't trust the order of values
-		// (2) it filters out values like EMAILADDRESS
-		String dn = cert.getSubjectDN().getName();
 		Map<String, String> oids = new HashMap<String, String>();
-		for (String kvp : dn.split(",")) {
-			String [] val = kvp.trim().split("=");
-			String oid = val[0].toUpperCase().trim();
-			String data = val[1].trim();
-			oids.put(oid, data);
+		try {
+			String dn = cert.getSubjectDN().getName();
+			LdapName ldapName = new LdapName(dn);
+			for (int i = 0; i < ldapName.size(); i++) {
+				String [] val = ldapName.get(i).trim().split("=", 2);
+				String oid = val[0].toUpperCase().trim();
+				String data = val[1].trim();
+				oids.put(oid, data);
+			}
+		} catch (Exception e) {
+			throw new RuntimeException(e);
 		}
 
 		X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
diff --git a/src/main/java/com/gitblit/utils/cli/CmdLineParser.java b/src/main/java/com/gitblit/utils/cli/CmdLineParser.java
index e698eb5..09ae836 100644
--- a/src/main/java/com/gitblit/utils/cli/CmdLineParser.java
+++ b/src/main/java/com/gitblit/utils/cli/CmdLineParser.java
@@ -429,5 +429,15 @@
 		public boolean isMultiValued() {
 			return false;
 		}
+
+		@Override
+		public boolean help() {
+			return true;
+		}
+
+		@Override
+		public String[] forbids() {
+			return new String [0];
+		}
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/FilestoreUI.java b/src/main/java/com/gitblit/wicket/FilestoreUI.java
new file mode 100644
index 0000000..8837ba1
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/FilestoreUI.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 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;
+
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.FilestoreModel.Status;
+
+/**
+ * Common filestore ui methods and classes.
+ *
+ * @author Paul Martin
+ *
+ */
+public class FilestoreUI {
+
+	public static Label getStatusIcon(String wicketId, FilestoreModel item) {
+		return getStatusIcon(wicketId, item.getStatus());
+	}
+
+	public static Label getStatusIcon(String wicketId, Status status) {
+		Label label = new Label(wicketId);
+
+		switch (status) {
+		case Upload_Pending:
+			WicketUtils.setCssClass(label, "fa fa-spinner fa-fw file-negative");
+			break;
+		case Upload_In_Progress:
+			WicketUtils.setCssClass(label, "fa fa-spinner fa-spin fa-fw file-positive");
+			break;
+		case Available:
+			WicketUtils.setCssClass(label, "fa fa-check fa-fw file-positive");
+			break;
+		case Deleted:
+			WicketUtils.setCssClass(label, "fa fa-ban fa-fw file-negative");
+			break;
+		case Unavailable:
+			WicketUtils.setCssClass(label, "fa fa-times fa-fw file-negative");
+			break;
+		default:
+			WicketUtils.setCssClass(label, "fa fa-exclamation-triangle fa-fw file-negative");
+		}
+		WicketUtils.setHtmlTooltip(label, status.toString());
+
+		return label;
+	}
+	
+}
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
index d19630c..296c254 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -37,12 +37,14 @@
 import com.gitblit.extensions.GitblitWicketPlugin;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
 import com.gitblit.manager.IUserManager;
 import com.gitblit.tickets.ITicketService;
 import com.gitblit.transport.ssh.IPublicKeyManager;
@@ -62,6 +64,7 @@
 import com.gitblit.wicket.pages.EditTicketPage;
 import com.gitblit.wicket.pages.ExportTicketPage;
 import com.gitblit.wicket.pages.FederationRegistrationPage;
+import com.gitblit.wicket.pages.FilestorePage;
 import com.gitblit.wicket.pages.ForkPage;
 import com.gitblit.wicket.pages.ForksPage;
 import com.gitblit.wicket.pages.GitSearchPage;
@@ -90,7 +93,11 @@
 import com.gitblit.wicket.pages.TreePage;
 import com.gitblit.wicket.pages.UserPage;
 import com.gitblit.wicket.pages.UsersPage;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
 
+@Singleton
 public class GitBlitWebApp extends WebApplication implements GitblitWicketApp {
 
 	private final Class<? extends WebPage> homePageClass = MyDashboardPage.class;
@@ -98,6 +105,10 @@
 	private final Class<? extends WebPage> newRepositoryPageClass = NewRepositoryPage.class;
 
 	private final Map<String, CacheControl> cacheablePages = new HashMap<String, CacheControl>();
+
+	private final Provider<IPublicKeyManager> publicKeyManagerProvider;
+
+	private final Provider<ITicketService> ticketServiceProvider;
 
 	private final IStoredSettings settings;
 
@@ -113,8 +124,6 @@
 
 	private final IAuthenticationManager authenticationManager;
 
-	private final IPublicKeyManager publicKeyManager;
-
 	private final IRepositoryManager repositoryManager;
 
 	private final IProjectManager projectManager;
@@ -123,19 +132,29 @@
 
 	private final IGitblit gitblit;
 
+	private final IServicesManager services;
+	
+	private final IFilestoreManager filestoreManager;
+
+	@Inject
 	public GitBlitWebApp(
+			Provider<IPublicKeyManager> publicKeyManagerProvider,
+			Provider<ITicketService> ticketServiceProvider,
 			IRuntimeManager runtimeManager,
 			IPluginManager pluginManager,
 			INotificationManager notificationManager,
 			IUserManager userManager,
 			IAuthenticationManager authenticationManager,
-			IPublicKeyManager publicKeyManager,
 			IRepositoryManager repositoryManager,
 			IProjectManager projectManager,
 			IFederationManager federationManager,
-			IGitblit gitblit) {
+			IGitblit gitblit,
+			IServicesManager services,
+			IFilestoreManager filestoreManager) {
 
 		super();
+		this.publicKeyManagerProvider = publicKeyManagerProvider;
+		this.ticketServiceProvider = ticketServiceProvider;
 		this.settings = runtimeManager.getSettings();
 		this.xssFilter = runtimeManager.getXssFilter();
 		this.runtimeManager = runtimeManager;
@@ -143,11 +162,12 @@
 		this.notificationManager = notificationManager;
 		this.userManager = userManager;
 		this.authenticationManager = authenticationManager;
-		this.publicKeyManager = publicKeyManager;
 		this.repositoryManager = repositoryManager;
 		this.projectManager = projectManager;
 		this.federationManager = federationManager;
 		this.gitblit = gitblit;
+		this.services = services;
+		this.filestoreManager = filestoreManager;
 	}
 
 	@Override
@@ -224,6 +244,9 @@
 		mount("/user", UserPage.class, "user");
 		mount("/forks", ForksPage.class, "r");
 		mount("/fork", ForkPage.class, "r");
+		
+		// filestore URL
+		mount("/filestore", FilestorePage.class);
 
 		// allow started Wicket plugins to initialize
 		for (PluginWrapper pluginWrapper : pluginManager.getPlugins()) {
@@ -392,7 +415,7 @@
 	 */
 	@Override
 	public IPublicKeyManager keys() {
-		return publicKeyManager;
+		return publicKeyManagerProvider.get();
 	}
 
 	/* (non-Javadoc)
@@ -428,11 +451,19 @@
 	}
 
 	/* (non-Javadoc)
+	 * @see com.gitblit.wicket.Webapp#services()
+	 */
+	@Override
+	public IServicesManager services() {
+		return services;
+	}
+
+	/* (non-Javadoc)
 	 * @see com.gitblit.wicket.Webapp#tickets()
 	 */
 	@Override
 	public ITicketService tickets() {
-		return gitblit.getTicketService();
+		return ticketServiceProvider.get();
 	}
 
 	/* (non-Javadoc)
@@ -454,4 +485,9 @@
 	public static GitBlitWebApp get() {
 		return (GitBlitWebApp) WebApplication.get();
 	}
+
+	@Override
+	public IFilestoreManager filestore() {
+		return filestoreManager;
+	}
 }
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index eb92e2d..36c416e 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -561,6 +561,7 @@
 gb.enhancementTickets = enhancements
 gb.taskTickets = tasks
 gb.questionTickets = questions
+gb.maintenanceTickets = maintenance
 gb.requestTickets = enhancements & tasks
 gb.yourCreatedTickets = created by you
 gb.yourWatchedTickets = watched by you
@@ -741,4 +742,32 @@
 gb.permission = Permission
 gb.sshKeyPermissionDescription = Specify the access permission for the SSH key
 gb.transportPreference = Transport Preference
-gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
\ No newline at end of file
+gb.transportPreferenceDescription = Set the transport that you prefer to use for cloning
+gb.priority = priority
+gb.severity = severity
+gb.sortHighestPriority = highest priority
+gb.sortLowestPriority = lowest priority
+gb.sortHighestSeverity = highest severity
+gb.sortLowestSeverity = lowest severity
+gb.missingIntegrationBranchMore = The target integration branch does not exist in the repository!
+gb.diffDeletedFileSkipped = (deleted)
+gb.diffFileDiffTooLarge = Diff too large
+gb.diffNewFile = New file
+gb.diffDeletedFile = File was deleted
+gb.diffRenamedFile = File was renamed from {0}
+gb.diffCopiedFile = File was copied from {0}
+gb.diffTruncated = Diff truncated after the above file
+gb.opacityAdjust = Adjust opacity
+gb.blinkComparator = Blink comparator
+gb.imgdiffSubtract = Subtract (black = identical)
+gb.deleteRepositoryHeader = Delete Repository
+gb.deleteRepositoryDescription = Deleted repositories will be unrecoverable.
+gb.show_whitespace = show whitespace
+gb.ignore_whitespace = ignore whitespace
+gb.allRepositories = All Repositories
+gb.oid = object id
+gb.filestore = filestore
+gb.filestoreStats = Filestore contains {0} files with a total size of {1}.  ({2} remaining)
+gb.statusChangedOn = status changed on
+gb.statusChangedBy = status changed by
+gb.filestoreHelp = How to use the Filestore?
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
index 3ec330b..eca3fd2 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_de.properties
@@ -743,3 +743,13 @@
 gb.sshKeyPermissionDescription = Geben Sie die Zugriffberechtigung f\u00fcr den SSH Key an
 gb.transportPreference = \u00dcbertragungseinstellungen
 gb.transportPreferenceDescription = Geben Sie die \u00dcbertragungsart an, die Sie f\u00fcr das Klonen bevorzugen
+gb.diffDeletedFileSkipped = (gel\u00f6scht)
+gb.diffFileDiffTooLarge = Zu viele \u00c4nderungen; Diff wird nicht angezeigt
+gb.diffNewFile = Neue Datei
+gb.diffDeletedFile = Datei wurde gel\u00f6scht
+gb.diffRenamedFile = Datei umbenannt von {0}
+gb.diffCopiedFile = Datei kopiert von {0}
+gb.diffTruncated = Diff nach obiger Datei abgeschnitten
+gb.opacityAdjust = Transparenz
+gb.blinkComparator = Blinkkomparator
+gb.imgdiffSubtract = Pixeldifferenz (schwarz = identisch)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
index b888bee..d479b3d 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_fr.properties
@@ -672,3 +672,13 @@
 gb.mergeToDescription = branche d'int\u00e9gration par d\u00e9faut pour fusionner les correctifs li\u00e9s aux tickets
 gb.myTickets = mes tickets
 gb.yourAssignedTickets = dont vous \u00eates responsable
+gb.diffDeletedFileSkipped = (effac\u00e9)
+gb.diffFileDiffTooLarge = Trop de diff\u00e9rences, affichage supprim\u00e9e
+gb.diffNewFile = Nouveau fichier
+gb.diffDeletedFile = Fichier a \u00e9t\u00e9 effac\u00e9
+gb.diffRenamedFile = Fichier renomm\u00e9 de {0}
+gb.diffCopiedFile = Fichier copi\u00e9 de {0}
+gb.diffTruncated = Affichage de diff\u00e9rences supprim\u00e9e apr\u00e8s le fichier ci-dessus
+gb.opacityAdjust = ajuster l'opacit\u00e9
+gb.blinkComparator = Comparateur \u00e0 clignotement
+gb.imgdiffSubtract = Diff\u00e9rence (noir = identique)
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
new file mode 100644
index 0000000..16a9c86
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp_zh_TW.properties
@@ -0,0 +1,772 @@
+#!
+#! created/edited by Popeye version 0.54 (popeye.sourceforge.net)
+#! encoding:ISO-8859-1
+gb.about = \u95dc\u65bc
+gb.acceptNewPatchsets = \u5141\u8a31\u88dc\u4e01
+gb.acceptNewPatchsetsDescription = \u63a5\u53d7\u5230\u7248\u672c\u5009\u9032\u884c\u4fee\u88dc\u52d5\u4f5c
+gb.acceptNewTickets = \u5141\u8a31\u5efa\u7acb\u4efb\u52d9\u55ae
+gb.acceptNewTicketsDescription = \u5141\u8a31\u65b0\u589e"\u81ed\u87f2","\u512a\u5316","\u4efb\u52d9"\u5404\u985e\u578b\u4efb\u52d9\u55ae
+gb.accessDenied = \u62d2\u7d55\u5b58\u53d6
+gb.accessLevel = \u5b58\u53d6\u7b49\u7d1a
+gb.accessPermissions = \u5b58\u53d6\u6b0a\u9650
+gb.accessPermissionsDescription = restrict access by users and teams
+gb.accessPermissionsForTeamDescription = set team members and grant access to specific restricted repositories
+gb.accessPermissionsForUserDescription = set team memberships or grant access to specific restricted repositories
+gb.accessPolicy = \u5b58\u53d6\u653f\u7b56
+gb.accessPolicyDescription = \u9078\u64c7\u7528\u4f86\u63a7\u5236\u6587\u4ef6\u5eab\u7684\u5b58\u53d6\u653f\u7b56\u4ee5\u53ca\u6b0a\u9650\u8a2d\u5b9a
+gb.accessRestriction = \u9650\u5236\u5b58\u53d6
+gb.accountPreferences = \u5e33\u865f\u8a2d\u5b9a
+gb.accountPreferencesDescription = \u8a2d\u5b9a\u5e33\u865f\u9810\u8a2d\u503c
+gb.action = \u52d5\u4f5c
+gb.active = \u6d3b\u52d5
+gb.activeAuthors = \u6d3b\u8e8d\u7528\u6236
+gb.activeRepositories = \u6d3b\u8e8d\u7248\u672c\u5eab
+gb.activity = \u6d3b\u52d5
+gb.add = \u65b0\u589e
+gb.addComment = \u65b0\u589e\u8a3b\u89e3
+gb.addedNCommits = {0}\u500b\u6a94\u6848\u63d0\u4ea4\u5b8c\u7562
+gb.addedOneCommit = \u63d0\u4ea41\u500b\u6a94\u6848
+gb.addition = addition
+gb.addSshKey = \u65b0\u589e SSH Key
+gb.administration = \u7ba1\u7406\u6b0a\u9650
+gb.administrator = \u7ba1\u7406\u54e1
+gb.administratorPermission = Gitblit \u7ba1\u7406\u54e1
+gb.affiliationChanged = affiliation changed
+gb.age = \u6642\u9593
+gb.all = \u5168\u90e8
+gb.allBranches = \u6240\u6709\u5206\u652f
+gb.allowAuthenticatedDescription = \u6279\u51c6 RW+ \u6b0a\u9650\u7d66\u4e88\u5c08\u6848\u6210\u54e1
+gb.allowForks = \u5141\u8a31\u5efa\u7acb\u5206\u652f(forks)
+gb.allowForksDescription = \u5141\u8a31\u5df2\u6388\u6b0a\u7684\u4f7f\u7528\u8005\u5f9e\u6587\u4ef6\u5eab\u5efa\u7acb\u5206\u652f(fork)
+gb.allowNamedDescription = grant fine-grained permissions to named users or teams
+gb.allProjects = \u5168\u90e8\u7fa4\u7d44
+gb.allTags = \u6240\u6709\u6a19\u7c64
+gb.anonymousCanNotPropose = \u533f\u540d\u8005\u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
+gb.anonymousPolicy = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
+gb.anonymousPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u6587\u4ef6\u5230\u6587\u4ef6\u5eab
+gb.anonymousUser= \u533f\u540d\u72c0\u614b
+gb.any = \u4efb\u4f55
+gb.approve = \u901a\u904e
+gb.at = at
+gb.attributes = \u5c6c\u6027
+gb.authenticatedPushPolicy = Restrict Push (Authenticated)
+gb.authenticatedPushPolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u6aa2\u8996\u8207\u8907\u88fd(clone).\u6240\u6709\u6587\u4ef6\u5eab\u6210\u54e1\u7686\u6709RW+\u8207\u63a8\u9001(push)\u529f\u80fd.
+gb.author = \u4f5c\u8005
+gb.authored = \u5df2\u6388\u6b0a
+gb.authorizationControl = \u6388\u6b0a\u7ba1\u63a7
+gb.available = \u53ef\u7528
+gb.blame = \u7a76\u67e5
+gb.blinkComparator = Blink comparator
+gb.blob = \u5340\u584a
+gb.body = body
+gb.bootDate = \u555f\u52d5\u65e5
+gb.branch = \u5206\u652f
+gb.branches = \u5206\u652f
+gb.branchStats = \u9019\u500b\u5206\u652f{2}\u6709{0}\u500b\u63d0\u4ea4\u4ee5\u53ca{1}\u500b\u6a19\u7c64
+gb.browse = \u700f\u89bd
+gb.bugTickets = \u81ed\u87f2
+gb.busyCollectingGarbage = \u62b1\u6b49,Gitblit\u6b63\u5728\u56de\u6536\u7cfb\u7d71\u8cc7\u6e90\u4e2d:{0}
+gb.byNAuthors = \u7d93\u7531{0}\u500b\u4f5c\u8005
+gb.byOneAuthor = \u7d93\u7531{0}
+gb.caCompromise = CA compromise
+gb.canAdmin = \u53ef\u7ba1\u7406
+gb.canAdminDescription = \u53ef\u7ba1\u7406Gitblit\u4f3a\u670d\u5668
+gb.cancel = \u53d6\u6d88
+gb.canCreate = \u53ef\u5efa\u7acb
+gb.canCreateDescription = \u80fd\u5920\u5efa\u7acb\u79c1\u4eba\u6587\u4ef6\u5eab
+gb.canFork = \u53ef\u5efa\u7acb\u5206\u652f(fork)
+gb.canForkDescription = \u53ef\u4ee5\u5efa\u7acb\u6587\u4ef6\u5eab\u5206\u652f(fork),\u4e26\u4e14\u8907\u88fd\u5230\u79c1\u4eba\u6587\u4ef6\u5eab\u4e2d
+gb.canNotLoadRepository = \u7121\u6cd5\u8f09\u5165\u7248\u672c\u5eab
+gb.canNotProposePatchset = \u4e0d\u80fd\u63d0\u4f9b\u88dc\u4e01
+gb.certificate = \u8b49\u66f8
+gb.certificateRevoked = \u8b49\u66f8{0,number,0} \u5df2\u7d93\u88ab\u53d6\u6d88
+gb.certificates = \u8b49\u66f8
+gb.cessationOfOperation = cessation of operation
+gb.changedFiles = \u5df2\u8b8a\u66f4\u904e\u7684\u6a94\u6848
+gb.changedStatus = changed the status
+gb.changePassword = \u4fee\u6539\u5bc6\u78bc
+gb.checkout = \u6aa2\u51fa(checkout)
+gb.checkoutStep1 = Fetch the current patchset \u2014 run this from your project directory
+gb.checkoutStep2 = \u5c07\u8a72\u88dc\u4e01\u8f49\u51fa\u5230\u65b0\u7684\u5206\u652f\u7136\u5f8c\u7528\u4f86\u6aa2\u8996
+gb.checkoutViaCommandLine = \u4f7f\u7528\u6307\u4ee4Checkout
+gb.checkoutViaCommandLineNote = \u4f60\u53ef\u4ee5\u5f9e\u4f60\u6587\u4ef6\u5eab\u4e2dcheckout\u4e00\u4efd,\u7136\u5f8c\u9032\u884c\u6e2c\u8a66
+gb.clearCache = \u6e05\u9664\u5feb\u53d6
+gb.clientCertificateBundleSent = {0}\u7684\u7528\u6236\u8b49\u66f8\u5df2\u5bc4\u767c
+gb.clientCertificateGenerated = \u6210\u529f\u7522\u751f{0}\u7684\u65b0\u8b49\u66f8
+gb.clone = \u8907\u88fd(clone)
+gb.clonePermission = {0} \u8907\u88fd(clone)
+gb.clonePolicy = Restrict Clone & Push
+gb.clonePolicyDescription = \u4efb\u4f55\u4eba\u53ef\u4ee5\u770b\u6587\u4ef6\u5eab. \u4f46\u4f60\u80fd\u5920\u8907\u88fd(clone)\u8207\u63a8\u9001(push)
+gb.cloneRestricted = authenticated clone & push
+gb.closeBrowser = \u8acb\u95dc\u9589\u700f\u89bd\u5668\u7d50\u675f\u6b64\u767b\u5165\u968e\u6bb5
+gb.closed = \u95dc\u9589
+gb.closedMilestones = \u5df2\u95dc\u9589\u7684\u91cc\u7a0b\u7891(milestones)
+gb.combinedMd5Rename = Gitblit\u4f7f\u7528md5\u65b9\u5f0f\u5c07\u5bc6\u78bc\u7de8\u78bc(\u7121\u6cd5\u9084\u539f).\u4f60\u5fc5\u9808\u8f38\u5165\u65b0\u5bc6\u78bc.
+gb.comment = \u8a3b\u89e3
+gb.commented = \u5df2\u8a3b\u89e3
+gb.comments = \u8a3b\u89e3
+gb.commit = \u63d0\u4ea4
+gb.commitActivityAuthors = \u63d0\u4ea4\u6d3b\u8e8d\u7387(\u4f7f\u7528\u8005)
+gb.commitActivityDOW = \u6bcf(\u65e5)\u9031\u63d0\u4ea4
+gb.commitActivityTrend = \u63d0\u4ea4\u8da8\u52e2\u5716
+gb.commitdiff = \u63d0\u4ea4\u5dee\u7570
+gb.commitIsNull = \u63d0\u4ea4\u5167\u5bb9\u662f\u7a7a\u7684
+gb.commitMessageRenderer = \u63d0\u4ea4\u8a0a\u606f\u5448\u73fe\u65b9\u5f0f
+gb.commitMessageRendererDescription = \u63d0\u4ea4\u8a0a\u606f\u53ef\u4ee5\u4f7f\u7528\u6587\u5b57\u6216\u662f\u6a19\u8a18\u8a9e\u8a00(markup)\u5448\u73fe
+gb.commits = \u63d0\u4ea4
+gb.commitsInPatchsetN = \u88dc\u4e01 {0} \u7684\u63d0\u4ea4
+gb.commitsTo = {0} commits to
+gb.committed = \u5df2\u63d0\u4ea4
+gb.committer = \u78ba\u8a8d\u63d0\u4ea4\u8005
+gb.compare = \u6bd4\u5c0d
+gb.compareToMergeBase = \u6bd4\u5c0d\u5f8c,\u5408\u4f75\u5230\u4e3b\u8981\u5de5\u4f5c\u5340
+gb.compareToN = \u8207{0}\u9032\u884c\u6bd4\u5c0d
+gb.completeGravatarProfile = \u5b8c\u6210Gravator.com\u4e0a\u7684\u57fa\u672c\u8cc7\u6599\u8a2d\u5b9a
+gb.confirmPassword = \u78ba\u8a8d\u5bc6\u78bc
+gb.content = \u5167\u5bb9
+gb.copyToClipboard = \u8907\u88fd\u5230\u526a\u8cbc\u677f
+gb.couldNotCreateFederationProposal = \u7121\u6cd5\u5efa\u7acb\u4e32\u9023\u7684\u5408\u4f5c\u63d0\u6848
+gb.couldNotFindFederationProposal = \u641c\u5c0b\u4e0d\u5230\u8981\u6c42\u4e32\u9023\u7684\u63d0\u6848
+gb.couldNotFindFederationRegistration = \u627e\u4e0d\u5230\u4e32\u9023\u8a3b\u518a\u55ae
+gb.couldNotFindTag = \u627e\u4e0d\u5230\u6a19\u7c64{0}
+gb.countryCode = \u570b\u5bb6\u4ee3\u78bc
+gb.create = \u5efa\u7acb
+gb.createdBy = created by
+gb.createdNewBranch = \u5efa\u7acb\u65b0\u5206\u652f
+gb.createdNewPullRequest = created pull request
+gb.createdNewTag = \u5efa\u7acb\u65b0\u6a19\u7c64
+gb.createdThisTicket = \u5df2\u958b\u7acb\u7684\u4efb\u52d9\u55ae
+gb.createFirstTicket = \u6309\u6b64\u9996\u767c\u4efb\u52d9\u55ae
+gb.createPermission = {0} (push, ref creation)
+gb.createReadme = \u5efa\u7acbREADME\u6a94\u6848
+gb.customFields = custom fields
+gb.customFieldsDescription = custom fields available to Groovy hooks
+gb.dailyActivity = \u6bcf\u65e5\u6d3b\u52d5
+gb.dashboard = \u5100\u8868\u677f
+gb.date = \u65e5\u671f
+gb.default = \u9810\u8a2d
+gb.delete = \u522a\u9664
+gb.deletedBranch = deleted branch
+gb.deletedTag = \u522a\u9664\u6a19\u7c64
+gb.deleteMilestone = \u522a\u9664\u91cc\u7a0b\u7891"{0}"?
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.deleteRepository = \u522a\u9664\u7248\u672c\u5eab"{0}"?
+gb.deleteRepositoryDescription = \u7248\u672c\u5eab\u522a\u9664\u5c07\u7121\u6cd5\u9084\u539f
+gb.deleteRepositoryHeader = \u522a\u9664\u7248\u672c\u5eab
+gb.deleteUser = \u522a\u9664\u4f7f\u7528\u8005"{0}"?
+gb.deletion = \u522a\u9664
+gb.description = \u6982\u8ff0
+gb.destinationUrl = \u50b3\u9001
+gb.destinationUrlDescription = \u50b3\u9001Gitblit\u9023\u7d50\u5230\u4f60\u7684\u5c08\u6848(proposal)
+gb.diff = \u5dee\u7570
+gb.diffCopiedFile = File was copied from {0}
+gb.diffDeletedFile = \u6a94\u6848\u5df2\u522a\u9664
+gb.diffDeletedFileSkipped = (\u522a\u9664)
+gb.diffFileDiffTooLarge = \u6a94\u6848\u592a\u5927
+gb.diffNewFile = \u6bd4\u5c0d\u65b0\u6a94\u6848
+gb.diffRenamedFile = File was renamed from {0}
+gb.diffStat = \u65b0\u589e{0}\u5217\u8207\u522a\u9664{1}\u5217
+gb.difftocurrent = \u6bd4\u5c0d\u5dee\u7570
+gb.diffTruncated = Diff truncated after the above file
+gb.disableUser = \u505c\u7528\u5e33\u6236
+gb.disableUserDescription = \u8a72\u5e33\u6236\u7121\u6cd5\u4f7f\u7528
+gb.discussion = \u8a0e\u8ad6
+gb.displayName = \u986f\u793a\u7684\u540d\u7a31
+gb.displayNameDescription = \u5e0c\u671b\u986f\u793a\u7684\u540d\u7a31
+gb.docs = \u6a94\u6848\u5340
+gb.docsWelcome1 = \u4f60\u53ef\u4ee5\u4f7f\u7528\u6a94\u6848\u5340\u5efa\u7acb\u6587\u4ef6\u5eab\u7684\u6559\u5b78\u6a94\u6848
+gb.docsWelcome2 = \u63d0\u4ea4README.md \u6216 HOME.md\u5f8c,\u518d\u958b\u59cb\u65b0\u7684\u6587\u4ef6\u5eab
+gb.doesNotExistInTree = {0}\u4e26\u6c92\u6709\u5728\u76ee\u9304{1}\u88e1\u9762
+gb.download = \u4e0b\u8f09
+gb.downloading = \u4e0b\u8f09ing
+gb.due = due
+gb.duration = \u9031\u671f
+gb.duration.days = {0}\u5929
+gb.duration.months = {0}\u6708
+gb.duration.oneDay = 1\u5929
+gb.duration.oneMonth = 1\u6708
+gb.duration.oneYear = 1\u5e74
+gb.duration.years = {0}\u5e74
+gb.edit = \u7de8\u8f2f
+gb.editMilestone = \u4fee\u6539milestone
+gb.editTicket = \u4fee\u6539\u4efb\u52d9\u55ae
+gb.editUsers = \u4fee\u6539\u5e33\u865f
+gb.effective = \u6240\u6709\u6b0a\u9650
+gb.emailAddress = \u96fb\u5b50\u90f5\u4ef6
+gb.emailAddressDescription = \u7528\u4f86\u63a5\u6536\u901a\u77e5\u7684\u4e3b\u8981\u96fb\u5b50\u90f5\u4ef6
+gb.emailCertificateBundle = \u5bc4\u767c\u7528\u6236\u7aef\u8b49\u66f8
+gb.emailMeOnMyTicketChanges = \u6211\u7684\u4efb\u52d9\u55ae\u82e5\u6709\u8b8a\u66f4,\u8acb800\u91cc\u52a0\u6025(email)\u901a\u77e5\u6211
+gb.emailMeOnMyTicketChangesDescription  = \u6211\u8655\u7406\u904e\u7684\u4efb\u52d9\u55ae\u8acbemail\u901a\u77e5\u6211
+gb.empty = \u7a7a\u7684
+gb.emptyRepository = \u7a7a\u7684\u7248\u672c\u5eab
+gb.enableDocs = \u555f\u7528\u6a94\u6848\u5340
+gb.enableIncrementalPushTags = \u555f\u7528\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u529f\u80fd
+gb.enableTickets = \u555f\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71
+gb.enhancementTickets = \u512a\u5316
+gb.enterKeystorePassword = \u8acb\u8f38\u5165Gitblit\u7684keystore\u5c08\u7528\u5bc6\u78bc
+gb.error = \u932f\u8aa4
+gb.errorAdministrationDisabled = \u7ba1\u7406\u6b0a\u9650\u5df2\u53d6\u6d88
+gb.errorAdminLoginRequired = \u767b\u5165\u9700\u6709\u7ba1\u7406\u6b0a\u9650
+gb.errorOnlyAdminMayCreateRepository = \u53ea\u6709\u7ba1\u7406\u8005\u80fd\u5efa\u7acb\u7248\u672c\u5eab
+gb.errorOnlyAdminOrOwnerMayEditRepository = \u53ea\u6709\u7ba1\u7406\u8005\u8207\u7248\u672c\u5eab\u64c1\u6709\u8005\u80fd\u4fee\u6539\u7248\u672c\u5eab\u5c6c\u6027
+gb.excludeFromActivity = exclude from activity page
+gb.excludeFromFederation = \u6392\u9664\u4e32\u9023
+gb.excludeFromFederationDescription = \u963b\u64cb\u5df2\u4e32\u9023\u7684Gitblit\u4f3a\u670d\u5668
+gb.excludePermission = {0} (\u6392\u9664)
+gb.exclusions = \u6392\u9664
+gb.expired = \u904e\u671f
+gb.expires = \u5230\u671f
+gb.expiring = \u5c07\u8981\u904e\u671f
+gb.export = \u532f\u51fa
+gb.extensions = \u64f4\u5145
+gb.externalPermissions = {0} access permissions are externally maintained
+gb.failedToFindAccount = \u7121\u6cd5\u641c\u5c0b\u5230\u5e33\u865f"{0}"
+gb.failedToFindCommit = Failed to find commit "{0}" in {1}\!
+gb.failedToFindGravatarProfile = \u7121\u6cd5\u627e\u5230\u5e33\u865f{0}\u7684Gravator\u8cc7\u6599
+gb.failedtoRead = \u8b80\u53d6\u5931\u6557
+gb.failedToReadMessage = Failed to read default message from {0}\!
+gb.failedToSendProposal = \u63d0\u6848\u767c\u9001\u5931\u6557\!
+gb.failedToUpdateUser = \u7121\u6cd5\u66f4\u65b0\u4f7f\u7528\u8005\u5e33\u865f
+gb.federatedRepositoryDefinitions =  \u7248\u672c\u5eab\u5b9a\u7fa9
+gb.federatedSettingDefinitions = setting definitions
+gb.federatedUserDefinitions = user definitions
+gb.federateOrigin = federate the origin
+gb.federateThis = \u8207\u672c\u6587\u4ef6\u5eab\u4e32\u9023
+gb.federation = \u4e32\u9023
+gb.federationRegistration = federation registration
+gb.federationRepositoryDescription = \u8207\u5176\u4ed6gitblit\u4f3a\u670d\u5668\u5206\u4eab\u4e00\u8d77\u4f7f\u7528\u9019\u500b\u7248\u672c\u5eab
+gb.federationResults = federation pull results
+gb.federationSets = \u4e32\u9023\u7d44\u5408
+gb.federationSetsDescription = \u6b64\u6587\u4ef6\u5eab\u5c07\u5305\u542b\u65bc\u6307\u5b9a\u7684\u4e32\u9023\u7fa4\u7d44(federation sets)
+gb.federationStrategy = \u4e32\u9023\u7b56\u7565
+gb.federationStrategyDescription = \u63a7\u5236\u5982\u4f55\u5c07\u6587\u4ef6\u5eab\u8207\u5176\u4ed6Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u4e32\u9023
+gb.feed = \u8cc7\u6599\u8a02\u95b1
+gb.filesAdded = \u65b0\u589e{0}\u500b\u6a94\u6848
+gb.filesCopied = \u8907\u88fd{0}\u500b\u6a94\u6848
+gb.filesDeleted = \u522a\u9664{0}\u500b\u6a94\u6848
+gb.filesModified = \u4fee\u6539{0}\u500b\u6a94\u6848
+gb.filesRenamed = \u4fee\u6539{0}\u500b\u6a94\u6848\u540d\u7a31
+gb.filter = \u689d\u4ef6\u904e\u6ffe
+gb.filters = \u67e5\u8a62\u689d\u4ef6
+gb.findSomeRepositories = \u641c\u5c0b\u6587\u4ef6\u5eab
+gb.folder = \u76ee\u9304
+gb.fork = \u5efa\u7acb\u5206\u652f(fork)
+gb.forkedFrom = forked from
+gb.forkInProgress = fork in progress
+gb.forkNotAuthorized = \u5f88\u62b1\u6b49, \u4f60\u7121\u5efa\u7acb\u6587\u4ef6\u5eab{0}\u5206\u652f(fork)\u7684\u6b0a\u9650
+gb.forkRepository = \u7248\u672c\u5eab{0}\u5efa\u7acb\u5206\u652f(fork)?
+gb.forks = \u5206\u652f(forks)
+gb.forksProhibited = \u7981\u6b62\u5efa\u7acb\u5206\u652f(forks)
+gb.forksProhibitedWarning = \u672c\u6587\u4ef6\u5eab\u7981\u6b62\u5206\u652f(fork)
+gb.free = \u91cb\u653e
+gb.frequency = \u983b\u7387
+gb.from = from
+gb.garbageCollection = \u56de\u6536\u7cfb\u7d71\u8cc7\u6e90
+gb.garbageCollectionDescription = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u529f\u80fd\u5c07\u6703\u6574\u9813\u9b06\u6563\u7528\u6236\u7aef\u63a8\u9001(push)\u7684\u7269\u4ef6, \u4e5f\u6703\u79fb\u9664\u6587\u4ef6\u5eab\u4e0a\u7121\u7528\u7684\u7269\u4ef6
+gb.gc = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u5668
+gb.gcPeriod = \u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u968e\u6bb5
+gb.gcPeriodDescription = \u56de\u6536\u9031\u671f
+gb.gcThreshold = GC \u57fa\u6578(threshold)
+gb.gcThresholdDescription = \u89f8\u767c\u7cfb\u7d71\u8cc7\u6e90\u56de\u6536\u7684\u6700\u5c0f\u7269\u4ef6\u5bb9\u91cf
+gb.general = \u4e00\u822c
+gb.generalDescription = \u4e00\u822c\u8a2d\u5b9a
+gb.hasNotReviewed = \u5c1a\u672a\u6aa2\u6838\u904e
+gb.head = HEAD
+gb.headRef = \u9810\u8a2d\u5206\u652f(HEAD)
+gb.headRefDescription = \u9810\u8a2d\u5206\u652f\u5c07\u6703\u8907\u88fd\u4ee5\u53ca\u986f\u793a\u5230\u532f\u7e3d\u9801\u9762
+gb.heapAllocated = \u5df2\u4f7f\u7528\u5806\u7a4d(Heap)
+gb.heapMaximum = \u6700\u5927\u5806\u7a4d(heap)
+gb.heapUsed = \u5df2\u4f7f\u7528\u7684\u5806\u7a4d(heap)
+gb.history = \u6b77\u7a0b
+gb.home = \u9996\u9801
+gb.hookScripts = hook\u7684\u8173\u672c
+gb.hookScriptsDescription = \u7576\u63a8\u9001(push)\u81f3\u6b64Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u6642, \u57f7\u884cGroovy\u8173\u672c
+gb.hostname = \u4e3b\u6a5f\u540d\u7a31
+gb.hostnameRequired = \u8acb\u8f38\u5165\u4e3b\u6a5f\u540d\u7a31
+gb.ignore_whitespace =\u5ffd\u7565\u7a7a\u767d
+gb.illegalCharacterRepositoryName = \u7248\u672c\u5eab\u540d\u7a31\u6709\u4e0d\u5408\u6cd5\u7684\u5b57\u5143"{0}"
+gb.illegalLeadingSlash = \u7981\u6b62\u6839\u76ee\u9304(/)
+gb.illegalPersonalRepositoryLocation = \u4f60\u79c1\u4eba\u7248\u672c\u5eab\u5fc5\u9808\u653e\u5728"{0}"
+gb.illegalRelativeSlash = \u7981\u6b62\u76f8\u5c0d\u76ee\u9304(../)
+gb.imgdiffSubtract = Subtract (black = identical)
+gb.in = in
+gb.inclusions = inclusions
+gb.incrementalPushTagMessage = \u7576[{0}]\u5206\u652f\u63a8\u9001\u5f8c,\u81ea\u52d5\u7d66\u4e88\u6a19\u7c64\u865f.
+gb.indexedBranches = \u5206\u652f\u7d22\u5f15
+gb.indexedBranchesDescription = \u9078\u5b9a\u6b32\u57f7\u884cLucene\u7d22\u5f15\u529f\u80fd\u7684\u5206\u652f
+gb.inherited = \u7e7c\u627f
+gb.initialCommit = \u521d\u6b21\u63d0\u4ea4
+gb.initialCommitDescription = \u4ee5\u4e0b\u6b65\u9a5f\u5c07\u6703\u8b93\u4f60\u99ac\u4e0a\u57f7\u884c<code>git clone</code>.\u5982\u679c\u4f60\u672c\u6a5f\u5df2\u6709\u6b64\u6587\u4ef6\u5eab\u4e14\u57f7\u884c\u904e<code>git init</code>,\u8acb\u8df3\u904e\u6b64\u6b65\u9a5f.
+gb.initWithGitignore = \u5305\u542b .gitignore \u6a94\u6848
+gb.initWithGitignoreDescription = \u65b0\u589e\u4e00\u500b\u8a2d\u5b9a\u6a94\u7528\u4f86\u6307\u5b9a\u54ea\u4e9b\u6a94\u6848\u6216\u76ee\u9304\u9700\u8981\u5ffd\u7565
+gb.initWithReadme = \u5305\u542bREADME\u6587\u4ef6
+gb.initWithReadmeDescription = \u6587\u4ef6\u5eab\u5c07\u7522\u751f\u7c21\u55aeREADME\u6587\u4ef6
+gb.invalidExpirationDate = \u4e0d\u6b63\u78ba\u7684\u5230\u671f\u65e5
+gb.invalidUsernameOrPassword = \u932f\u8aa4\u7684\u4f7f\u7528\u8005\u540d\u7a31\u6216\u5bc6\u78bc!
+gb.isFederated = \u5df2\u7d93\u4e32\u9023
+gb.isFork = \u662f\u5206\u652f\u985e\u578b(fork)
+gb.isFrozen = \u51cd\u7d50\u63a5\u6536
+gb.isFrozenDescription = \u7981\u6b62\u63a8\u9001(push)
+gb.isMirror = \u8a72\u6587\u4ef6\u5eab\u70ba\u93e1\u50cf(mirror)
+gb.isNotValidFile = \u4e0d\u662f\u6b63\u5e38\u6a94\u6848
+gb.isSparkleshared = \u8a72\u6587\u4ef6\u5eab\u5df2\u70baSparkleshared (http://sparkleshare.org)
+gb.issued = \u767c\u51fa
+gb.issuer = issuer
+gb.jceWarning = Your Java Runtime Environment does not have the "JCE Unlimited Strength Jurisdiction Policy" files.\nThis will limit the length of passwords you may use to encrypt your keystores to 7 characters.\nThese policy files are an optional download from Oracle.\n\nWould you like to continue and generate the certificate infrastructure anyway?\n\nAnswering No will direct your browser to Oracle's download page so that you may download the policy files.
+gb.key = \u91d1\u9470
+gb.keyCompromise = \u91d1\u9470\u5bc6\u78bc\u5916\u6d29
+gb.labels = \u6a19\u8a18
+gb.languagePreference = \u5e38\u7528\u8a9e\u8a00
+gb.languagePreferenceDescription = \u9078\u64c7\u4f60\u60f3\u8981\u7684Gitblit\u7ffb\u8b6f
+gb.lastChange = \u6700\u8fd1\u4fee\u6539
+gb.lastLogin = \u6700\u8fd1\u767b\u5165
+gb.lastNDays = \u6700\u8fd1{0}\u5929
+gb.lastPull = \u4e0a\u6b21\u4e0b\u8f09(pull)
+gb.leaveComment = \u7559\u4e0b\u8a3b\u89e3
+gb.line = \u884c
+gb.loading = \u8f09\u5165
+gb.local = \u672c\u5730\u7aef
+gb.locality = \u4f4d\u7f6e
+gb.log = \u65e5\u8a8c
+gb.login = \u767b\u5165
+gb.logout = \u767b\u51fa
+gb.looksGood = \u770b\u8d77\u4f86\u5f88\u597d
+gb.luceneDisabled = \u505c\u7528Lucene\u7d22\u5f15\u529f\u80fd
+gb.mailingLists = \u90f5\u4ef6\u540d\u55ae
+gb.maintenanceTickets = \u7dad\u8b77
+gb.manage = \u7ba1\u7406
+gb.manual = \u81ea\u884c\u8f38\u5165
+gb.markdown = markdown
+gb.markdownFailure = \u89e3\u6790Markdown\u5931\u6557
+gb.maxActivityCommits = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u7387
+gb.maxActivityCommitsDescription = \u6700\u5927\u63d0\u4ea4\u6d3b\u8e8d\u6578\u91cf
+gb.maxHits = \u6700\u5927\u9ede\u64ca
+gb.md5FingerPrint = MD5 Fingerprint
+gb.mentions = \u63d0\u5230
+gb.mentionsMeTickets = \u63d0\u5230\u4f60
+gb.merge = \u5408\u4f75
+gb.mergeBase = \u57fa\u672c\u5408\u4f75
+gb.merged = \u5df2\u5408\u4f75
+gb.mergedPatchset = \u5c07\u88dc\u4e01\u5408\u4f75
+gb.mergedPullRequest = \u5408\u4f75\u63a8\u9001\u8981\u6c42
+gb.mergeSha = mergeSha
+gb.mergeStep1 = Check out a new branch to review the changes \u2014 run this from your project directory
+gb.mergeStep2 = Bring in the proposed changes and review
+gb.mergeStep3 = \u5c07\u63d0\u6848\u4fee\u6539\u5167\u5bb9\u5408\u4f75\u5230\u4f3a\u670d\u5668\u4e0a
+gb.mergeTo = \u5408\u4f75\u5230
+gb.mergeToDescription = \u9810\u8a2d\u5c07\u6587\u4ef6\u76f8\u95dc\u88dc\u4e01\u5305\u8207\u6307\u5b9a\u5206\u652f(branch)\u5408\u4f75
+gb.mergingViaCommandLine = \u7d93\u7531\u6307\u4ee4\u57f7\u884c\u5408\u4f75
+gb.mergingViaCommandLineNote = \u5982\u679c\u4f60\u4e0d\u60f3\u8981\u4f7f\u7528\u81ea\u52d5\u5408\u4f75\u529f\u80fd,\u6216\u662f\u6309\u4e0b\u5408\u4f75\u6309\u9215, \u4f60\u53ef\u4ee5\u4e0b\u6307\u4ee4\u624b\u52d5\u5408\u4f75
+gb.message = \u8a0a\u606f
+gb.metricAuthorExclusions = \u91cf\u5316\u7d71\u8a08\u6642\u6392\u9664\u6d3b\u8e8d\u5e33\u6236
+gb.metrics = \u91cf\u5316\u7d71\u8a08
+gb.milestone = \u91cc\u7a0b\u7891
+gb.milestoneDeleteFailed = \u522a\u9664\u91cc\u7a0b\u7891"{0}"\u5931\u6557
+gb.milestoneProgress = {0}\u958b\u555f,{1}\u7d50\u675f
+gb.milestones = \u91cc\u7a0b\u7891
+gb.mirrorOf = {0}\u7684\u93e1\u50cf
+gb.mirrorWarning = \u8a72\u6587\u4ef6\u5eab\u5c6c\u65bc\u93e1\u50cf, \u4e0d\u80fd\u5920\u63a5\u6536\u63a8\u9001(push)
+gb.miscellaneous = \u5176\u4ed6
+gb.missing = \u5931\u8aa4!
+gb.missingIntegrationBranchMore = \u76ee\u6a19\u5206\u652f\u4e0d\u5728\u6b64\u7248\u672c\u5eab
+gb.missingPermission = the repository for this permission is missing\!
+gb.missingUsername = \u7f3a\u5c11\u4f7f\u7528\u8005\u540d\u7a31
+gb.modification = \u4fee\u6539
+gb.monthlyActivity = \u6708\u6d3b\u52d5
+gb.moreChanges = \u6240\u6709\u8b8a\u66f4...
+gb.moreHistory = \u66f4\u591a\u6b77\u53f2\u7d00\u9304...
+gb.moreLogs = \u66f4\u591a\u63d0\u4ea4 ...
+gb.mutable = \u52d5\u614b\u7d66\u4e88
+gb.myDashboard = \u6211\u7684\u5100\u8868\u677f
+gb.myFork = \u6aa2\u8996\u6211\u5efa\u7acb\u7684\u5206\u652f(fork)
+gb.myProfile = \u6211\u7684\u57fa\u672c\u8cc7\u6599
+gb.myRepositories = \u6211\u7684\u7248\u672c\u5eab
+gb.myTickets = \u6211\u7684\u4efb\u52d9\u55ae
+gb.myUrlDescription = \u4f60Gitblit\u4f3a\u670d\u5668\u7684\u516c\u958bURL
+gb.name = \u540d\u5b57
+gb.nameDescription = \u4f7f\u7528"/"\u505a\u70ba\u6587\u4ef6\u5eab\u7fa4\u7d44\u5206\u985e. \u5982: library/mycoolib.git
+gb.namedPushPolicy = Restrict Push (Named)
+gb.namedPushPolicyDescription = \u4efb\u4f55\u4eba\u7686\u53ef\u6aa2\u8996\u8207\u8907\u88fd(clone)\u6587\u4ef6\u5eab. \u4f60\u53ef\u53e6\u5916\u6307\u5b9a\u8ab0\u80fd\u5920\u6709\u63a8\u9001\u529f\u80fd(push)
+gb.nAttachments = {0}\u500b\u9644\u4ef6
+gb.nClosedTickets = {0}\u9805\u7d50\u675f
+gb.nComments = {0}\u500b\u8a3b\u89e3
+gb.nCommits = {0}\u4efd\u63d0\u4ea4
+gb.needsImprovement = \u9700\u8981\u512a\u5316
+gb.new = \u5efa\u7acb
+gb.newCertificate = \u5efa\u7acb\u8b49\u66f8
+gb.newCertificateDefaults = \u65b0\u8b49\u66f8\u9810\u8a2d\u503c
+gb.newClientCertificateMessage = \u6ce8\u610f:\n'password'\u5bc6\u78bc\u4e26\u4e0d\u662f\u4f7f\u7528\u8005\u5bc6\u78bc, \u800c\u662f\u7528\u4f86\u4fdd\u8b77\u4f7f\u7528\u8005\u500b\u4eba\u7684keystore.\u8a72\u5bc6\u78bc\u4e26\u4e0d\u6703\u5132\u5b58,  \u56e0\u6b64\u5fc5\u9808\u8a2d\u5b9a\u63d0\u793a(hint), \u8a72\u63d0\u793a\u5c07\u6703\u5beb\u5728\u4f7f\u7528\u8005\u7684README\u6587\u4ef6\u88e1\u9762.
+gb.newMilestone = \u5efa\u7acb\u91cc\u7a0b\u7891
+gb.newRepository = \u5efa\u7acb\u7248\u672c\u5eab
+gb.newSSLCertificate = \u65b0\u7684\u4f3a\u670d\u5668SSL\u8b49\u66f8
+gb.newTeam = \u5efa\u7acb\u5718\u968a
+gb.newTicket = \u65b0\u589e\u4efb\u52d9\u55ae
+gb.newUser = \u5efa\u7acb\u4f7f\u7528\u8005
+gb.nextPull = next pull
+gb.nFederationProposalsToReview = \u7e3d\u5171\u6709{0}\u500b\u4e32\u9023\u8a08\u756b\u7b49\u5f85\u5be9\u8996
+gb.nMoreCommits = \u9084\u6709{0}\u4efd\u63d0\u4ea4 \u00bb
+gb.noActivity = \u904e\u53bb{0}\u5929\u4f86,\u4e26\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
+gb.noActivityToday = \u4eca\u5929\u6c92\u6709\u6d3b\u52d5\u7d00\u9304
+gb.noComments = \u6c92\u6709\u5099\u8a3b
+gb.noDescriptionGiven = \u6c92\u6709\u7d66\u4e88\u7c21\u8ff0
+gb.noFederation = Sorry, {0} is not configured to federate with any Gitblit instances.
+gb.noForks = {0}\u6c92\u6709\u5206\u652f(fork)
+gb.noGitblitFound = Sorry, {0} could not find a Gitblit instance at {1}.
+gb.noHits = \u7121\u9ede\u64ca
+gb.noIndexedRepositoriesWarning = \u8ddf\u4f60\u76f8\u95dc\u7684\u6587\u4ef6\u5eab\u4e26\u6c92\u6709\u505aLucene\u7d22\u5f15
+gb.noMaximum = \u7121\u6700\u5927\u503c
+gb.noMilestoneSelected = \u672a\u9078\u53d6\u91cc\u7a0b\u7891
+gb.none = \u7121
+gb.nOpenTickets = {0}\u9805\u958b\u555f\u4e2d
+gb.noPermission = \u522a\u9664\u9019\u500b\u6b0a\u9650
+gb.noProposals = \u62b1\u6b49, {0}\u6b64\u6642\u4e26\u4e0d\u662f\u53ef\u63a5\u53d7\u7684\u8a08\u756b
+gb.noSelectedRepositoriesWarning = \u8acb\u81f3\u5c11\u9078\u64c7\u4e00\u500b\u6587\u4ef6\u5eab
+gb.notifyChangedOpenTickets = \u5df2\u958b\u555f\u7684\u4efb\u52d9\u55ae\u6709\u7570\u52d5\u8acb\u767c\u9001\u901a\u77e5
+gb.notRestricted = \u533f\u540d\u72c0\u614b\u53ef\u4ee5View, Clone\u8207Push
+gb.notSpecified = \u7121\u6307\u5b9a
+gb.nParticipants = {0}\u500b\u53c3\u8207
+gb.nTotalTickets = \u7e3d\u5171{0}\u9805
+gb.object = \u7269\u4ef6
+gb.of = \u7684
+gb.ok = ok
+gb.oneAttachment  = {0}\u500b\u9644\u4ef6
+gb.oneComment = {0}\u500b\u8a3b\u89e3
+gb.oneCommit = 1\u500b\u63d0\u4ea4
+gb.oneCommitTo = 1\u500b\u63d0\u4ea4\u5230
+gb.oneMoreCommit = \u9084\u6709\u4e00\u500b\u63d0\u4ea4  \u00bb
+gb.oneParticipant = {0}\u53c3\u8207
+gb.OneProposalToReview = \u6709\u4e00\u500b\u4e32\u9023\u7684\u63d0\u6848\u7b49\u5f85\u5be9\u67e5
+gb.opacityAdjust = Adjust opacity
+gb.open = \u958b\u555f
+gb.openMilestones = \u6253\u958b\u91cc\u7a0b\u7891
+gb.organization = \u7d44\u7e54
+gb.organizationalUnit = \u7d44\u7e54\u55ae\u4f4d
+gb.origin = origin
+gb.originDescription = \u6b64\u6587\u4ef6\u5eabURL\u5df2\u7d93\u88ab\u8907\u88fd(cloned)\u4e86
+gb.overdue = \u904e\u671f
+gb.overview = \u6982\u89c0
+gb.owned = \u64c1\u6709\u7684
+gb.owner = \u64c1\u6709\u8005
+gb.ownerDescription = \u64c1\u6709\u8005\u53ef\u4fee\u6539\u6587\u4ef6\u5eab\u8a2d\u5b9a\u503c
+gb.ownerPermission = \u6587\u4ef6\u5eab\u6240\u6709\u8005
+gb.owners = \u6240\u6709\u8005
+gb.ownersDescription = \u6240\u6709\u8005\u53ef\u4ee5\u7ba1\u7406\u6587\u4ef6\u5eab,\u4f46\u662f\u4e0d\u5141\u8a31\u4fee\u6539\u540d\u7a31(\u79c1\u4eba\u6587\u4ef6\u5eab\u4f8b\u5916)
+gb.pageFirst = \u7b2c\u4e00\u7b46
+gb.pageNext = \u4e0b\u4e00\u9801
+gb.pagePrevious = \u4e0a\u4e00\u9801
+gb.pages = \u6587\u4ef6
+gb.parent = \u4e0a\u500b\u7248\u672c
+gb.password = \u5bc6\u78bc
+gb.passwordChangeAborted = \u53d6\u6d88\u5bc6\u78bc\u8b8a\u66f4
+gb.passwordChanged = \u5bc6\u78bc\u8b8a\u66f4\u6210\u529f
+gb.passwordHint = \u5bc6\u78bc\u63d0\u793a
+gb.passwordHintRequired = \u5bc6\u78bc\u63d0\u793a(\u5fc5\u8981)
+gb.passwordsDoNotMatch = \u5bc6\u78bc\u4e0d\u76f8\u7b26
+gb.passwordTooShort = \u5bc6\u78bc\u904e\u77ed, \u6700\u5c11{0}\u500b\u5b57\u5143
+gb.patch = \u4fee\u88dc\u6a94
+gb.patchset = \u88dc\u4e01
+gb.patchsetAlreadyMerged = \u8a72\u88dc\u4e01\u5df2\u7d93\u5408\u4f75\u5230{0}
+gb.patchsetMergeable = \u8a72\u88dc\u4e01\u53ef\u4ee5\u81ea\u52d5\u8207{0}\u5408\u4f75
+gb.patchsetMergeableMore = \u4f7f\u7528\u547d\u4ee4\u529f\u80fd,\u8b93\u6b64\u88dc\u4e01\u53ef\u4ee5\u8207{0}\u5408\u4f75
+gb.patchsetN = \u88dc\u4e01{0}
+gb.patchsetNotApproved = \u8a72\u88dc\u4e01\u7248\u672c\u4e26\u6c92\u6709\u88ab\u6279\u51c6\u8207{0}\u5408\u4f75
+gb.patchsetNotApprovedMore = \u8a72\u88dc\u4e01\u5fc5\u9808\u7531\u5be9\u67e5\u8005\u6279\u51c6
+gb.patchsetNotMergeable = \u8a72\u88dc\u4e01\u4e0d\u80fd\u81ea\u52d5\u8207{0}\u5408\u4f75
+gb.patchsetNotMergeableMore = \u5fc5\u9808\u4ee5rebased\u6216\u662f\u624b\u52d5\u8207{0}\u5408\u4f75\u7684\u65b9\u5f0f\u624d\u80fd\u89e3\u6c7a\u8a72\u88dc\u4e01\u9020\u6210\u7684\u885d\u7a81
+gb.patchsetVetoedMore = \u5be9\u8996\u8005\u5df2\u7d93\u5c0d\u6b64\u88dc\u4e01\u6295\u7968
+gb.permission = \u6b0a\u9650
+gb.permissions = \u6b0a\u9650
+gb.permittedTeams = permitted teams
+gb.permittedUsers = permitted users
+gb.personalRepositories = \u500b\u4eba\u6587\u4ef6\u5eab
+gb.pleaseGenerateClientCertificate = \u8acb\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684\u7528\u6236\u7aef\u8b49\u66f8
+gb.pleaseSelectGitIgnore = \u8acb\u9078\u64c7\u4e00\u500b.gitignore\u6a94\u6848
+gb.pleaseSelectProject = \u8acb\u9078\u64c7\u5c08\u6848!
+gb.pleaseSetDestinationUrl = Please enter a destination url for your proposal\!
+gb.pleaseSetGitblitUrl = \u8acb\u8f38\u5165Gitblit URL !
+gb.pleaseSetRepositoryName = \u8acb\u8a2d\u5b9a\u7248\u672c\u5eab\u540d\u7a31
+gb.pleaseSetTeamName = \u8acb\u8f38\u5165\u5718\u968a\u540d\u7a31
+gb.pleaseSetUsername = \u8acb\u8f38\u5165\u4f7f\u7528\u8005\u540d\u7a31
+gb.plugins = \u63d2\u4ef6
+gb.postReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4e26\u4e14\u5728refs\u5b8c\u7562\u5f8c</em>, \u5c07\u6703\u57f7\u884cPost-receive hook..<p>This is the appropriate hook for notifications, build triggers, etc.</p>
+gb.postReceiveScripts = post-receive\u8173\u672c
+gb.preferences = \u9810\u8a2d\u5e38\u7528\u503c
+gb.preparingFork = \u6b63\u5728\u6e96\u5099\u8907\u88fd\u4e2d(fork)...
+gb.preReceiveDescription = \u63a5\u5230\u63d0\u4ea4\u7533\u8acb\u5f8c,<em>\u4f46\u5728\u9084\u6c92\u6709\u66f4\u65b0refs\u524d</em>, \u5c07\u6703\u57f7\u884cPre-receive hook. <p>This is the appropriate hook for rejecting a push.</p>
+gb.preReceiveScripts = pre-receive \u8173\u672c
+gb.preview = \u9810\u89bd
+gb.priority = \u512a\u5148
+gb.privilegeWithdrawn = \u53d6\u6d88\u6b0a\u9650
+gb.project = \u7fa4\u7d44
+gb.projects = \u7fa4\u7d44
+gb.properties = \u5c6c\u6027
+gb.proposal = \u63d0\u6848
+gb.proposalError = \u62b1\u6b49, {0} \u4efd\u5831\u544a\u767c\u751f\u9810\u671f\u5916\u7684\u932f\u8aa4!
+gb.proposalFailed = Sorry, {0} did not receive any proposal data\!
+gb.proposalReceived = Proposal successfully received by {0}.
+gb.proposals = \u8981\u6c42\u806f\u5408\u7684\u63d0\u6848
+gb.proposalTickets = \u63d0\u6848\u4fee\u6539
+gb.proposedThisChange = proposed this change
+gb.proposeInstructions = To start, craft a patchset and upload it with Git. Gitblit will link your patchset to this ticket by the id.
+gb.proposePatchset = \u63d0\u51fa\u88dc\u4e01
+gb.proposePatchsetNote = \u6b61\u8fce\u5c0d\u6b64\u4efb\u52d9\u55ae\u63d0\u4f9b\u88dc\u4e01
+gb.proposeWith = propose a patchset with {0}
+gb.ptCheckout = Fetch & checkout the current patchset to a review branch
+gb.ptDescription = the Gitblit patchset tool
+gb.ptDescription1 = Barnum is a command-line companion for Git that simplifies the syntax for working with Gitblit Tickets and Patchsets.
+gb.ptDescription2 = Barnum requires Python 3 and native Git. It runs on Windows, Linux, and Mac OS X.
+gb.ptMerge = \u53d6\u5f97\u76ee\u524d\u88dc\u4e01,\u7136\u5f8c\u8207\u4f60\u672c\u6a5f\u7aef\u7684\u5206\u652f\u5408\u4f75
+gb.ptSimplifiedCollaboration = simplified collaboration syntax
+gb.ptSimplifiedMerge = simplified merge syntax
+gb.publicKey = \u516c\u958b\u91d1\u9470
+gb.pushedNCommitsTo = {0}\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
+gb.pushedNewBranch = \u65b0\u5206\u652f\u5df2\u63a8\u9001(pushed)
+gb.pushedNewTag = \u65b0\u6a19\u7c64\u5df2\u63a8\u9001(pushed)
+gb.pushedOneCommitTo = 1\u500b\u63d0\u4ea4\u5df2\u63a8\u9001\u81f3
+gb.pushPermission = {0}(\u63a8\u9001)
+gb.pushRestricted = authenticated push
+gb.queries = \u67e5\u8a62\u7d50\u679c
+gb.query = \u67e5\u8a62
+gb.queryHelp = \u652f\u63f4\u6a19\u6e96\u67e5\u8a62\u8a9e\u6cd5.<p/><p/>\u8a73\u60c5\u8acb\u53c3\u8003 <a target\ = "_new" href\ = "http\://lucene.apache.org/core/old_versioned_docs/versions/3_5_0/queryparsersyntax.html">Lucene Query Parser Syntax</a>
+gb.queryResults = results {0} - {1} ({2} hits)
+gb.questionTickets = \u63d0\u554f
+gb.raw = \u539f\u59cb
+gb.reason = \u539f\u56e0
+gb.receive = \u63a5\u6536
+gb.received = \u5df2\u63a5\u6536
+gb.receiveSettings = \u8a2d\u5b9a\u63a5\u6536\u65b9\u5f0f
+gb.receiveSettingsDescription = \u63a7\u7ba1\u63a8\u9001\u5230\u6587\u4ef6\u5eab\u7684\u63a5\u6536\u65b9\u5f0f
+gb.recent = \u6700\u8fd1
+gb.recentActivity = \u6700\u8fd1\u6d3b\u8e8d\u72c0\u6cc1
+gb.recentActivityNone = \u904e\u53bb{0}\u5929/\u7121
+gb.recentActivityStats = \u904e\u53bb{0}\u5929,\u4e00\u5171\u6709{2}\u4eba\u57f7\u884c{1}\u4efd\u63d0\u4ea4
+gb.reflog = \u76f8\u95dc\u65e5\u8a8c
+gb.refresh = \u5237\u65b0
+gb.refs = \u5f15\u7528
+gb.regexPermission = \u5df2\u7d93\u4f7f\u7528\u6b63\u898f\u8868\u793a\u5f0f(regular expression)"{0}" \u8a2d\u5b9a\u6b0a\u9650\u5b8c\u7562
+gb.registration = \u8a3b\u518a
+gb.registrations = federation registrations
+gb.releaseDate = \u767c\u8868\u65e5
+gb.remote = \u9060\u7aef
+gb.removeVote = \u79fb\u9664\u6295\u7968
+gb.rename = \u6539\u540d\u7a31
+gb.repositories = \u6587\u4ef6\u5eab
+gb.repository = \u7248\u672c\u5eab
+gb.repositoryDeleted = \u7248\u672c\u5eab"{0}"\u5df2\u522a\u9664
+gb.repositoryDeleteFailed = \u522a\u9664\u7248\u672c\u5eab"{0}"\u5931\u6557!
+gb.repositoryDoesNotAcceptPatchsets = \u8a72\u7248\u672c\u5eab\u4e0d\u63a5\u53d7\u88dc\u4e01
+gb.repositoryForked = \u7248\u672c\u5eab{0}\u5df2\u7d93\u5efa\u7acb\u5206\u652f(fork)
+gb.repositoryForkFailed= \u5efa\u7acb\u5206\u652f(fork)\u5931\u6557
+gb.repositoryIsFrozen = \u8a72\u7248\u672c\u5eab\u5df2\u51cd\u7d50
+gb.repositoryIsMirror = \u8a72\u7248\u672c\u5eab\u70ba\u552f\u8b80\u8907\u672c
+gb.repositoryNotSpecified = \u672a\u6307\u5b9a\u7248\u672c\u5eab!
+gb.repositoryNotSpecifiedFor = \u7248\u672c\u5eab\u4e26\u6c92\u6709\u6307\u5b9a\u7d66 {0}\!
+gb.repositoryPermissions = \u7248\u672c\u5eab\u6b0a\u9650
+gb.repositoryUrl = \u7248\u672c\u5eab url
+gb.requestTickets = \u512a\u5316 & \u4efb\u52d9
+gb.requireApproval = \u9700\u6279\u51c6
+gb.requireApprovalDescription = \u5408\u4f75\u6309\u9215\u555f\u7528\u524d,\u88dc\u4e01\u5305\u5fc5\u9808\u5148\u6279\u51c6
+gb.reset = \u6e05\u9664
+gb.responsible = \u8ca0\u8cac\u4eba\u54e1
+gb.restrictedRepositories = restricted repositories
+gb.review = \u8907\u67e5(review)
+gb.reviewedPatchsetRev = reviewed patchset {0} revision {1}\: {2}
+gb.reviewers = \u5be9\u67e5\u8005
+gb.reviewPatchset = review {0} patchset {1}
+gb.reviews = reviews
+gb.revisionHistory = \u4fee\u6539\u7d00\u9304
+gb.revokeCertificate = \u64a4\u56de\u8b49\u66f8
+gb.revokeCertificateReason = \u8acb\u8f38\u5165\u64a4\u56de\u8b49\u66f8\u7406\u7531
+gb.revoked = \u5df2\u64a4\u92b7
+gb.rewind = REWIND
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
+gb.save = \u5132\u5b58
+gb.search = \u641c\u5c0b
+gb.searchForAuthor = Search for commits authored by
+gb.searchForCommitter = Search for commits committed by
+gb.searchTickets = \u641c\u5c0b\u4efb\u52d9\u55ae
+gb.searchTicketsTooltip = \u627e\u5230{0}\u4efd\u4efb\u52d9\u55ae
+gb.searchTooltip = \u641c\u5c0b{0}
+gb.searchTypeTooltip = \u9078\u64c7\u641c\u5c0b\u985e\u578b
+gb.selectAccessRestriction = Please select access restriction\!
+gb.selected = \u9078\u5b9a
+gb.selectFederationStrategy = Please select federation strategy\!
+gb.sendEmail = \u767cemail
+gb.sendProposal = \u63d0\u6848
+gb.serialNumber = \u5e8f\u865f
+gb.serveCertificate = \u555f\u7528\u4f7f\u7528\u6b64\u8b49\u66f8\u7684https\u529f\u80fd
+gb.serverDoesNotAcceptPatchsets = \u672c\u4f3a\u670d\u5668\u4e0d\u63a5\u53d7\u88dc\u4e01
+gb.servers = \u4f3a\u670d\u5668
+gb.servletContainer = servlet\u5bb9\u5668
+gb.sessionEnded = session\u5df2\u7d93\u53d6\u6d88
+gb.setDefault = \u8a2d\u70ba\u9810\u8a2d\u503c
+gb.settings = \u8a2d\u5b9a
+gb.severity =  \u91cd\u8981
+gb.sha1FingerPrint = SHA-1 Fingerprint
+gb.show_whitespace = \u986f\u793a\u7a7a\u767d
+gb.showHideDetails = \u986f\u793a/\u96b1\u85cf \u8a73\u89e3\u5167\u5bb9
+gb.showReadme = \u986f\u793areadme\u6587\u4ef6
+gb.showReadmeDescription = \u5728\u532f\u7e3d\u9801\u9762\u4e2d\u986f\u793a"readme"(markdown\u683c\u5f0f)
+gb.showRemoteBranches = \u986f\u793a\u9060\u7aef\u5206\u652f
+gb.showRemoteBranchesDescription = \u986f\u793a\u9060\u7aef\u5206\u652f(branches)
+gb.signatureAlgorithm = \u7c3d\u7ae0\u6f14\u7b97\u6cd5
+gb.since = \u5f9e
+gb.siteName = \u7ad9\u53f0\u540d\u7a31
+gb.siteNameDescription = \u4f3a\u670d\u5668\u7c21\u7a31
+gb.size = \u5bb9\u91cf
+gb.skipSizeCalculation = \u7565\u904e\u5bb9\u91cf\u8a08\u7b97
+gb.skipSizeCalculationDescription = \u4e0d\u8a08\u7b97\u6587\u4ef6\u5eab\u5bb9\u91cf(\u52a0\u5feb\u7db2\u9801\u8f09\u5165\u901f\u5ea6)
+gb.skipSummaryMetrics = \u7565\u904e\u91cf\u5316\u532f\u7e3d
+gb.skipSummaryMetricsDescription = \u4e0d\u8981\u8a08\u7b97\u91cf\u5316\u4e26\u4e14\u986f\u793a\u5728\u532f\u7e3d\u9801\u9762\u4e0a(\u52a0\u5feb\u901f\u5ea6)
+gb.sort = \u6392\u5e8f
+gb.sortHighestPriority = \u6700\u9ad8\u512a\u5148
+gb.sortHighestSeverity = \u6700\u91cd\u8981
+gb.sortLeastComments = \u6700\u5c11\u5099\u8a3b
+gb.sortLeastPatchsetRevisions = \u6700\u5c11\u88dc\u4e01\u4fee\u6539
+gb.sortLeastRecentlyUpdated = \u6700\u8fd1\u6700\u5c11\u8b8a\u52d5
+gb.sortLeastVotes = \u6700\u5c11\u6295\u7968
+gb.sortLowestPriority = \u6700\u4f4e\u512a\u5148
+gb.sortLowestSeverity = \u6700\u4e0d\u91cd\u8981
+gb.sortMostComments = \u6700\u591a\u5099\u8a3b
+gb.sortMostPatchsetRevisions = \u6700\u591a\u88dc\u4e01\u4fee\u6b63
+gb.sortMostRecentlyUpdated = \u6700\u8fd1\u66f4\u65b0
+gb.sortMostVotes = \u6700\u591a\u6295\u7968
+gb.sortNewest = \u6700\u65b0
+gb.sortOldest = \u6700\u820a
+gb.specified = \u6307\u5b9a\u7d66\u4e88(\u542b\u7cfb\u7d71\u9810\u8a2d)
+gb.sshKeyCommentDescription = \u8acb\u8f38\u5165\u5099\u8a3b, \u82e5\u7121\u5099\u8a3b, \u5c07\u81ea\u8a02\u586b\u5165key data
+gb.sshKeyPermissionDescription = \u6307\u5b9a\u8a72SSH key\u6240\u64c1\u6709\u7684\u5b58\u53d6\u6b0a\u9650
+gb.sshKeys = SSH Keys
+gb.sshKeysDescription = SSH \u516c\u958b\u91d1\u9470\u662f\u5bc6\u78bc\u8a8d\u8b49\u5916\u66f4\u5b89\u5168\u7684\u9078\u9805
+gb.sslCertificateGenerated = \u6210\u529f\u7522\u751f\u7d66{0}\u7684\u670d\u5668SSL\u8b49\u66f8
+gb.sslCertificateGeneratedRestart = \u6210\u529f\u7522\u751f\u7d66{0}\u4f7f\u7528\u7684SSL\u8b49\u66f8\n\u4f60\u5fc5\u9808\u91cd\u65b0\u555f\u52d5Gitblit\u7248\u63a7\u4f3a\u670d\u5668\u624d\u80fd\u555f\u7528\u65b0\u7684\u8b49\u66f8\n\nf you are launching with the '--alias' parameter you will have to set that to ''--alias {0}''.
+gb.star = \u91cd\u8981
+gb.stargazers = stargazers
+gb.starred = \u91cd\u8981
+gb.starredAndOwned = \u91cd\u8981\u7684 & \u64c1\u6709\u7684
+gb.starredRepositories = \u91cd\u8981\u7684\u6587\u4ef6\u5eab
+gb.starting = \u555f\u52d5\u4e2d
+gb.stateProvince = \u5dde\u6216\u7701
+gb.stats = \u7d71\u8a08
+gb.status = \u72c0\u614b
+gb.stepN = \u6b65\u9a5f{0}
+gb.stopWatching = \u505c\u6b62\u8ffd\u8e64(watching)
+gb.subject = \u6a19\u984c
+gb.subscribe = \u8a02\u95b1
+gb.summary = \u532f\u7e3d
+gb.superseded = \u5df2\u88ab\u66ff\u4ee3
+gb.tag = \u6a19\u7c64
+gb.tagger = tagger
+gb.tags = \u6a19\u7c64
+gb.taskTickets = \u4efb\u52d9
+gb.team = \u5718\u968a
+gb.teamCreated = \u5718\u968a"{0}"\u65b0\u589e\u6210\u529f.
+gb.teamMembers = \u5718\u968a\u6210\u54e1
+gb.teamMemberships = \u5718\u968a\u6210\u54e1(memberships)
+gb.teamMustSpecifyRepository = \u5718\u968a\u6700\u5c11\u8981\u6307\u5b9a\u4e00\u500b\u7248\u672c\u5eab
+gb.teamName = \u5718\u968a\u540d\u7a31
+gb.teamNameUnavailable = \u5718\u968a"{0}"\u4e0d\u5b58\u5728.
+gb.teamPermission = "{0}" \u5718\u968a\u6210\u54e1\u7684\u6b0a\u9650
+gb.teamPermissions = \u5718\u968a\u6b0a\u9650
+gb.teamPermissionsDescription = \u4f60\u53ef\u4ee5\u6307\u5b9a\u5718\u968a\u6b0a\u9650.\u9019\u4e9b\u8a2d\u5b9a\u5c07\u6703\u53d6\u4ee3\u539f\u672c\u5718\u968a\u9810\u8a2d\u6b0a\u9650
+gb.teams = \u53c3\u8207\u7684\u5718\u968a
+gb.ticket = \u4efb\u52d9\u55ae
+gb.ticketAssigned = \u5df2\u6307\u5b9a
+gb.ticketComments = \u8a3b\u89e3
+gb.ticketId = \u4efb\u52d9\u55aeID
+gb.ticketIsClosed = \u8a72\u4efb\u52d9\u55ae\u5df2\u7d93\u7d50\u6848
+gb.ticketN = \u4efb\u52d9\u55ae\u865f#{0}
+gb.ticketOpenDate = \u767c\u884c\u65e5
+gb.ticketPatchset = {0}\u4efb\u52d9\u55ae,{1}\u88dc\u4e01
+gb.tickets = \u4efb\u52d9\u55ae
+gb.ticketSettings = \u4efb\u52d9\u55ae\u5167\u5bb9\u8a2d\u5b9a
+gb.ticketStatus = \u72c0\u614b
+gb.ticketsWelcome = \u4f60\u53ef\u4ee5\u5229\u7528\u4efb\u52d9\u55ae\u7cfb\u7d71\u5efa\u69cb\u51fa\u5f85\u8fa6\u4e8b\u9805, \u81ed\u87f2\u56de\u5831\u5340\u4ee5\u53ca\u88dc\u4e01\u5305\u7684\u5354\u540c\u5408\u4f5c
+gb.time.daysAgo = {0}\u5929\u524d
+gb.time.hoursAgo = {0}\u5c0f\u6642\u524d
+gb.time.inDays = {0}\u5929\u5167
+gb.time.inHours = {0}\u5c0f\u6642\u5167
+gb.time.inMinutes = {0}\u5206\u9418\u5167
+gb.time.justNow = \u525b\u525b
+gb.time.minsAgo = {0}\u5206\u9418\u524d
+gb.time.monthsAgo = {0}\u6708\u524d
+gb.time.oneYearAgo = 1\u5e74\u524d
+gb.time.today = \u4eca\u5929
+gb.time.weeksAgo = {0}\u5468\u524d
+gb.time.yearsAgo = {0}\u5e74\u524d
+gb.time.yesterday = \u6628\u5929
+gb.title = \u6a19\u984c
+gb.to = to
+gb.toBranch = to {0}
+gb.todaysActivityNone = \u4eca\u5929/\u7121
+gb.todaysActivityStats = \u4eca\u5929/\u6709{2}\u500b\u4f5c\u8005\u5b8c\u6210{1}\u500b\u63d0\u4ea4
+gb.token = token
+gb.tokenAllDescription = \u6240\u6709\u7248\u672c\u5eab,\u4f7f\u7528\u8005\u8207\u8a2d\u5b9a
+gb.tokenJurDescription = \u6240\u6709\u7248\u672c\u5eab
+gb.tokens = federation tokens
+gb.tokenUnrDescription = \u6240\u6709\u7248\u672c\u5eab\u8207\u4f7f\u7528\u8005
+gb.topic = \u8a71\u984c
+gb.topicsAndLabels = \u8a71\u984c\u8207\u6a19\u8a18
+gb.transportPreference = \u9810\u8a2d\u901a\u8a0a\u5354\u5b9a
+gb.transportPreferenceDescription = \u8a2d\u5b9a\u4f60\u5e38\u7528\u7684\u9023\u7dda\u901a\u8a0a\u5354\u5b9a\u4ee5\u7528\u4f86\u8907\u88fd(clone)
+gb.tree = \u76ee\u9304
+gb.type = \u985e\u578b
+gb.unauthorizedAccessForRepository = \u7248\u672c\u5eab\u672a\u6388\u6b0a\u5b58\u53d6
+gb.undefinedQueryWarning = \u672a\u8a2d\u5b9a\u67e5\u8a62\u689d\u4ef6
+gb.unspecified = \u672a\u6307\u5b9a
+gb.unstar = \u53d6\u6d88
+gb.updated = \u5df2\u66f4\u65b0
+gb.updatedBy = updated by
+gb.uploadedPatchsetN = \u88dc\u4e01{0}\u5df2\u4e0a\u50b3
+gb.uploadedPatchsetNRevisionN = \u88dc\u4e01{0}\u4fee\u6539\u7248\u672c{1}\u5df2\u4e0a\u50b3
+gb.url = URL
+gb.useDocsDescription = \u8a08\u7b97\u6587\u4ef6\u5eab\u88e1\u9762\u7684Markdown\u6a94\u6848
+gb.useIncrementalPushTagsDescription = \u63a8\u9001\u6642\u5c07\u81ea\u52d5\u65b0\u589e\u6a19\u7c64\u865f\u78bc
+gb.userCreated = \u6210\u529f\u5efa\u7acb\u65b0\u4f7f\u7528\u8005"{0}"
+gb.userDeleted = \u4f7f\u7528\u8005"{0}"\u5df2\u522a\u9664
+gb.userDeleteFailed = \u4f7f\u7528\u8005"{0}"\u522a\u9664\u5931\u6557
+gb.username = \u4f7f\u7528\u8005\u540d\u7a31
+gb.usernameUnavailable = \u4f7f\u7528\u8005\u540d\u7a31"{0}"\u4e0d\u53ef\u7528
+gb.userPermissions = \u4f7f\u7528\u8005\u6b0a\u9650
+gb.userPermissionsDescription = \u4f60\u53ef\u4ee5\u91dd\u5c0d\u5e33\u865f\u8a2d\u5b9a\u6b0a\u9650(\u9019\u4e9b\u8a2d\u5b9a\u5c07\u8986\u84cb\u5718\u968a\u6216\u5176\u4ed6\u6b0a\u9650)
+gb.users = \u4f7f\u7528\u8005
+gb.userServiceDoesNotPermitAddUser = {0}\u4e0d\u5141\u8a31\u65b0\u589e\u4f7f\u7528\u8005\u5e33\u865f
+gb.userServiceDoesNotPermitPasswordChanges = {0}\u4e0d\u5141\u8a31\u4fee\u6539\u5bc6\u78bc
+gb.useTicketsDescription = readonly, distributed Ticgit issues
+gb.validFrom = valid from
+gb.validity = validity
+gb.validUntil = valid until
+gb.verifyCommitter = \u63d0\u4ea4\u8005\u9700\u9a57\u8b49
+gb.verifyCommitterDescription = \u9700\u8981\u63d0\u4ea4\u8005\u7b26\u5408\u63a8\u9001\u5e33\u865f
+gb.verifyCommitterNote = \u6240\u6709\u5408\u4f75\u52d5\u4f5c\u7686\u9808\u5f37\u5236\u4f7f\u7528"--no-ff"\u53c3\u6578
+gb.version = \u7248\u672c
+gb.veto = veto
+gb.view = \u6aa2\u8996
+gb.viewAccess = \u4f60\u6c92\u6709Gitblit\u8b80\u53d6\u6216\u662f\u4fee\u6539\u6b0a\u9650
+gb.viewCertificate = \u6aa2\u8996\u8b49\u66f8
+gb.viewComparison = \u6bd4\u8f03\u9019{0}\u500b\u63d0\u4ea4 \u00bb
+gb.viewPermission = {0} (\u6aa2\u8996)
+gb.viewPolicy  = Restrict View, Clone, & Push
+gb.viewPolicyDescription = \u9078\u64c7\u53ef\u4ee5\u5728\u6587\u4ef6\u5eab\u6aa2\u8996,\u8907\u88fd(clone)\u8207\u63a8\u9001(push)\u7684\u4f7f\u7528\u8005, \u9664\u6b64\u4e4b\u5916\u5176\u4ed6\u4eba\u7686\u7121\u6b0a\u9650
+gb.viewRestricted = authenticated view, clone, & push
+gb.vote = \u5c0d{0}\u6295\u7968
+gb.voters = votes
+gb.votes = votes
+gb.warning = \u8b66\u544a
+gb.watch = \u76e3\u770b{0}
+gb.watchers = \u76e3\u770b\u8005
+gb.watching = \u76e3\u770b\u4e2d
+gb.workingCopy = \u5de5\u4f5c\u8907\u672c
+gb.workingCopyWarning = \u8a72\u6587\u4ef6\u5eab\u4ecd\u6709\u5de5\u4f5c\u8907\u672c,\u56e0\u6b64\u7121\u6cd5\u63a5\u53d7\u63a8\u9001(push)
+gb.write = write
+gb.youDoNotHaveClonePermission = \u4f60\u4e0d\u5141\u8a31\u8907\u88fd(clone)\u6b64\u6587\u4ef6\u5eab
+gb.yourAssignedTickets = \u6307\u6d3e\u7d66\u4f60\u7684
+gb.yourCreatedTickets = \u7531\u4f60\u65b0\u589e\u7684
+gb.yourWatchedTickets = \u4f60\u60f3\u770b\u7684
+gb.zip = zip\u58d3\u7e2e\u6a94
+gb.ticketState =
+gb.repositoryForkFailed =
+gb.anonymousUser =
+gb.oneAttachment =
+gb.viewPolicy =
+gb.emailMeOnMyTicketChangesDescription =
diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketApp.java b/src/main/java/com/gitblit/wicket/GitblitWicketApp.java
index 8d3d598..fefa0f4 100644
--- a/src/main/java/com/gitblit/wicket/GitblitWicketApp.java
+++ b/src/main/java/com/gitblit/wicket/GitblitWicketApp.java
@@ -8,12 +8,14 @@
 import com.gitblit.IStoredSettings;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IPluginManager;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IServicesManager;
 import com.gitblit.manager.IUserManager;
 import com.gitblit.tickets.ITicketService;
 import com.gitblit.transport.ssh.IPublicKeyManager;
@@ -68,8 +70,12 @@
 
 	public abstract IGitblit gitblit();
 
+	public abstract IServicesManager services();
+
 	public abstract ITicketService tickets();
 
 	public abstract TimeZone getTimezone();
+	
+	public abstract IFilestoreManager filestore();
 
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
index 7865fb3..68ad84a 100644
--- a/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
+++ b/src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
@@ -17,6 +17,8 @@
 
 import java.util.Date;
 
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wicket.protocol.http.IWebApplicationFactory;
@@ -28,7 +30,6 @@
 
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
-import com.gitblit.dagger.DaggerWicketFilter;
 import com.gitblit.manager.IProjectManager;
 import com.gitblit.manager.IRepositoryManager;
 import com.gitblit.manager.IRuntimeManager;
@@ -36,8 +37,6 @@
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
-
-import dagger.ObjectGraph;
 
 /**
  *
@@ -47,7 +46,8 @@
  * @author James Moger
  *
  */
-public class GitblitWicketFilter extends DaggerWicketFilter {
+@Singleton
+public class GitblitWicketFilter extends WicketFilter {
 
 	private IStoredSettings settings;
 
@@ -59,13 +59,19 @@
 
 	private GitBlitWebApp webapp;
 
-	@Override
-	protected void inject(ObjectGraph dagger) {
-		this.settings = dagger.get(IStoredSettings.class);
-		this.runtimeManager = dagger.get(IRuntimeManager.class);
-		this.repositoryManager = dagger.get(IRepositoryManager.class);
-		this.projectManager = dagger.get(IProjectManager.class);
-		this.webapp = dagger.get(GitBlitWebApp.class);
+	@Inject
+	public GitblitWicketFilter(
+			IStoredSettings settings,
+			IRuntimeManager runtimeManager,
+			IRepositoryManager repositoryManager,
+			IProjectManager projectManager,
+			GitBlitWebApp webapp) {
+
+		this.settings = settings;
+		this.runtimeManager = runtimeManager;
+		this.repositoryManager = repositoryManager;
+		this.projectManager = projectManager;
+		this.webapp = webapp;
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java b/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java
new file mode 100644
index 0000000..1bf34e4
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/NonTrimmedPasswordTextField.java
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.apache.wicket.markup.html.form.PasswordTextField;
+import org.apache.wicket.model.IModel;
+
+/**
+ * PasswordText field which will not trim spaces from the input field. This 
+ * ensures the password trimming behaviour is everywhere (ui/ssh/git) the same 
+ * (#932).
+ */
+public class NonTrimmedPasswordTextField extends PasswordTextField
+{
+	private static final long serialVersionUID = 1L;
+	
+	public NonTrimmedPasswordTextField(final String id)
+	{
+		super(id);
+	}
+	
+	public NonTrimmedPasswordTextField(final String id, final IModel<String> model)
+	{
+		super(id, model);
+	}
+
+	@Override
+	protected boolean shouldTrimInput()
+	{
+		return false;
+	}
+
+}
diff --git a/src/main/java/com/gitblit/wicket/TicketsUI.java b/src/main/java/com/gitblit/wicket/TicketsUI.java
index 0eff4cf..8d59952 100644
--- a/src/main/java/com/gitblit/wicket/TicketsUI.java
+++ b/src/main/java/com/gitblit/wicket/TicketsUI.java
@@ -21,6 +21,8 @@
 import org.apache.wicket.markup.html.basic.Label;
 
 import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.models.TicketModel.Type;
 import com.gitblit.utils.StringUtils;
@@ -38,37 +40,74 @@
 	public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
 
 	public static Label getStateIcon(String wicketId, TicketModel ticket) {
-		return getStateIcon(wicketId, ticket.type, ticket.status);
+		return getStateIcon(wicketId, ticket.type, ticket.status, ticket.severity);
 	}
 
-	public static Label getStateIcon(String wicketId, Type type, Status state) {
+	public static Label getStateIcon(String wicketId, Type type, Status state, Severity severity) {
 		Label label = new Label(wicketId);
 		if (type == null) {
 			type = Type.defaultType;
 		}
 		switch (type) {
 		case Proposal:
-			WicketUtils.setCssClass(label, "fa fa-code-fork");
+			WicketUtils.setCssClass(label, "fa fa-code-fork fa-fw");
 			break;
 		case Bug:
-			WicketUtils.setCssClass(label, "fa fa-bug");
+			WicketUtils.setCssClass(label, "fa fa-bug fa-fw");
 			break;
 		case Enhancement:
-			WicketUtils.setCssClass(label, "fa fa-magic");
+			WicketUtils.setCssClass(label, "fa fa-magic fa-fw");
 			break;
 		case Question:
-			WicketUtils.setCssClass(label, "fa fa-question");
+			WicketUtils.setCssClass(label, "fa fa-question fa-fw");
+			break;
+		case Maintenance:
+			WicketUtils.setCssClass(label, "fa fa-cogs fa-fw");
 			break;
 		default:
 			// standard ticket
-			WicketUtils.setCssClass(label, "fa fa-ticket");
+			WicketUtils.setCssClass(label, "fa fa-ticket fa-fw");
 		}
-		WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
+		WicketUtils.setHtmlTooltip(label, getTypeState(type, state, severity));
+
 		return label;
 	}
 
-	public static String getTypeState(Type type, Status state) {
-		return state.toString() + " " + type.toString();
+	public static Label getPriorityIcon(String wicketId, Priority priority) {
+		Label label = new Label(wicketId);
+		if (priority == null) {
+			priority = Priority.defaultPriority;
+		}
+		switch (priority) {
+		case Urgent:
+			WicketUtils.setCssClass(label, "fa fa-step-forward fa-rotate-270");
+			break;
+		case High:
+			WicketUtils.setCssClass(label, "fa fa-caret-up fa-lg");
+			break;
+		case Low:
+			WicketUtils.setCssClass(label, "fa fa-caret-down fa-lg");
+			break;
+		default:
+		}
+		WicketUtils.setHtmlTooltip(label, priority.toString());
+
+		return label;
+	}
+
+	public static String getPriorityClass(Priority priority) {
+		return String.format("priority-%s", priority);
+	}
+
+	public static String getSeverityClass(Severity severity) {
+		return String.format("severity-%s", severity);
+	}
+
+	public static String getTypeState(Type type, Status state, Severity severity) {
+		if (Severity.Unrated == severity) {
+			return state.toString() + " " + type.toString();
+		}
+		return state.toString() + " " + type.toString() + ", " + severity.toString();
 	}
 
 	public static String getLozengeClass(Status status, boolean subtle) {
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
index d47390d..0d48e2b 100644
--- a/src/main/java/com/gitblit/wicket/WicketUtils.java
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -29,12 +29,14 @@
 import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.Request;
+import org.apache.wicket.behavior.AttributeAppender;
 import org.apache.wicket.behavior.HeaderContributor;
 import org.apache.wicket.behavior.SimpleAttributeModifier;
 import org.apache.wicket.markup.html.IHeaderContributor;
 import org.apache.wicket.markup.html.IHeaderResponse;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.image.ContextImage;
+import org.apache.wicket.model.Model;
 import org.apache.wicket.protocol.http.WebRequest;
 import org.apache.wicket.resource.ContextRelativeResource;
 import org.eclipse.jgit.diff.DiffEntry.ChangeType;
@@ -46,6 +48,7 @@
 import com.gitblit.Keys;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.Metric;
+import com.gitblit.utils.DiffUtils.DiffComparator;
 import com.gitblit.utils.HttpUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
@@ -54,6 +57,10 @@
 
 	public static void setCssClass(Component container, String value) {
 		container.add(new SimpleAttributeModifier("class", value));
+	}
+
+	public static void addCssClass(Component container, String value) {
+		container.add(new AttributeAppender("class", new Model<String>(value), " "));
 	}
 
 	public static void setCssStyle(Component container, String value) {
@@ -324,6 +331,31 @@
 		return new PageParameters(parameterMap);
 	}
 
+	public static PageParameters newDiffParameter(String repositoryName,
+			String objectId, DiffComparator diffComparator) {
+		Map<String, String> parameterMap = new HashMap<String, String>();
+		if (StringUtils.isEmpty(objectId)) {
+			return newRepositoryParameter(repositoryName);
+		}
+		parameterMap.put("r", repositoryName);
+		parameterMap.put("h", objectId);
+		parameterMap.put("w", "" + diffComparator.ordinal());
+		return new PageParameters(parameterMap);
+	}
+
+	public static PageParameters newDiffParameter(String repositoryName,
+			String objectId, DiffComparator diffComparator, String blobPath) {
+		Map<String, String> parameterMap = new HashMap<String, String>();
+		if (StringUtils.isEmpty(objectId)) {
+			return newRepositoryParameter(repositoryName);
+		}
+		parameterMap.put("r", repositoryName);
+		parameterMap.put("h", objectId);
+		parameterMap.put("w", "" + diffComparator.ordinal());
+		parameterMap.put("f", blobPath);
+		return new PageParameters(parameterMap);
+	}
+
 	public static PageParameters newRangeParameter(String repositoryName,
 			String startRange, String endRange) {
 		Map<String, String> parameterMap = new HashMap<String, String>();
@@ -488,6 +520,11 @@
 		return params.getString("st", null);
 	}
 
+	public static DiffComparator getDiffComparator(PageParameters params) {
+		int ordinal = params.getInt("w", 0);
+		return DiffComparator.values()[ordinal];
+	}
+
 	public static int getPage(PageParameters params) {
 		// index from 1
 		return params.getInt("pg", 1);
diff --git a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
index 1512ada..43816cf 100644
--- a/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
+++ b/src/main/java/com/gitblit/wicket/freemarker/templates/FilterableRepositoryList.fm
@@ -9,7 +9,13 @@
 	</div>
 	
 	<div ng-repeat="item in ${ngList} | filter:query" style="padding: 3px;border-top: 1px solid #ddd;">
-		<b><span class="repositorySwatch" style="background-color:{{item.c}};"><span ng-show="item.wc">!</span><span ng-show="!item.wc">&nbsp;</span></span></b>
+		<span style="color:{{item.c}};padding-right:2px;">
+            <span ng-show="item.y == 0" class="octicon octicon-centered octicon-repo"></span>
+            <span ng-show="item.y == 1" class="octicon octicon-centered octicon-repo-forked"></span>
+            <span ng-show="item.y == 2" class="octicon octicon-centered octicon-mirror"></span>
+            <span ng-show="item.y == 3" class="octicon octicon-centered octicon-repo-push"></span>
+        </span>
+    
 		<a href="summary/?r={{item.r}}" title="{{item.i}}">{{item.p}}<b>{{item.n}}</b></a>
 		<span class="link hidden-tablet hidden-phone" style="color: #bbb;" title="{{item.d}}">{{item.t}}</span>
 		<span ng-show="item.s" class="pull-right">
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.html b/src/main/java/com/gitblit/wicket/pages/BasePage.html
index 89a28b8..b998428 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.html
@@ -15,6 +15,7 @@
 		<link rel="stylesheet" href="bootstrap/css/bootstrap.css"/>
 		<link rel="stylesheet" href="bootstrap/css/iconic.css"/>
 		<link rel="stylesheet" href="fontawesome/css/font-awesome.min.css"/>
+        <link rel="stylesheet" href="octicons/octicons.css"/>
 		<link rel="stylesheet" type="text/css" href="gitblit.css"/>
 	</wicket:head>
 
@@ -50,5 +51,6 @@
 		<!-- Include scripts at end for faster page loading -->
 		<script type="text/javascript" src="bootstrap/js/jquery.js"></script>
 		<script type="text/javascript" src="bootstrap/js/bootstrap.js"></script>		
+		<wicket:container wicket:id="bottomScripts"></wicket:container>
 	</body>
 </html>
\ 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 b696700..0d99f5e 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -42,6 +42,8 @@
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.ExternalLink;
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
+import org.apache.wicket.markup.html.resources.JavascriptResourceReference;
+import org.apache.wicket.markup.repeater.RepeatingView;
 import org.apache.wicket.protocol.http.RequestUtils;
 import org.apache.wicket.protocol.http.WebResponse;
 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
@@ -242,7 +244,7 @@
 
 	protected void setupPage(String repositoryName, String pageName) {
 		add(new Label("title", getPageTitle(repositoryName)));
-
+		getBottomScriptContainer();
 		String rootLinkUrl = app().settings().getString(Keys.web.rootLink, urlFor(GitBlitWebApp.get().getHomePage(), null).toString());
 		ExternalLink rootLink = new ExternalLink("rootLink", rootLinkUrl);
 		WicketUtils.setHtmlTooltip(rootLink, app().settings().getString(Keys.web.siteName, Constants.NAME));
@@ -506,4 +508,40 @@
 		return sb.toString();
 	}
 
+	private RepeatingView getBottomScriptContainer() {
+		RepeatingView bottomScriptContainer = (RepeatingView) get("bottomScripts");
+		if (bottomScriptContainer == null) {
+			bottomScriptContainer = new RepeatingView("bottomScripts");
+			bottomScriptContainer.setRenderBodyOnly(true);
+			add(bottomScriptContainer);
+		}
+		return bottomScriptContainer;
+	}
+
+	/**
+	 * Adds a HTML script element loading the javascript designated by the given path.
+	 *
+	 * @param scriptPath
+	 *            page-relative path to the Javascript resource; normally starts with "scripts/"
+	 */
+	protected void addBottomScript(String scriptPath) {
+		RepeatingView bottomScripts = getBottomScriptContainer();
+		Label script = new Label(bottomScripts.newChildId(), "<script type='text/javascript' src='"
+				+ urlFor(new JavascriptResourceReference(this.getClass(), scriptPath)) + "'></script>\n");
+		bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
+	}
+
+	/**
+	 * Adds a HTML script element containing the given code.
+	 *
+	 * @param code
+	 *            inline script code
+	 */
+	protected void addBottomScriptInline(String code) {
+		RepeatingView bottomScripts = getBottomScriptContainer();
+		Label script = new Label(bottomScripts.newChildId(),
+				"<script type='text/javascript'>/*<![CDATA[*/\n" + code + "\n//]]>\n</script>\n");
+		bottomScripts.add(script.setEscapeModelStrings(false).setRenderBodyOnly(true));
+	}
+
 }
diff --git a/src/main/java/com/gitblit/wicket/pages/BlamePage.java b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
index 3c850f2..e45bbbc 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlamePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlamePage.java
@@ -154,6 +154,7 @@
 
 		add(new Label("missingBlob").setVisible(false));
 
+		final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
 		List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
 		final Map<?, String> colorMap = initializeColors(activeBlameType, lines);
 		ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
@@ -212,7 +213,7 @@
 					color = colorMap.get(entry.commitId);
 					break;
 				}
-				Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true)).setEscapeModelStrings(false);
+				Component data = new Label("data", StringUtils.escapeForHtml(entry.data, true, tabLength)).setEscapeModelStrings(false);
 				data.add(new SimpleAttributeModifier("style", "background-color: " + color + ";"));
 				item.add(data);
 			}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
index c633642..d218436 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.html
@@ -9,7 +9,7 @@
 	
 	<!-- blob nav links -->	
 	<div class="page_nav2">
-		<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a>
+		<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a> | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="commitDiffLink"><wicket:message key="gb.commitdiff"></wicket:message></a> | <a wicket:id="whitespaceLink"></a>
 	</div>	
 	
 	<!-- commit header -->
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
index 9cc3eae..adf815e 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -15,13 +15,17 @@
  */
 package com.gitblit.wicket.pages;
 
+import java.util.List;
+
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.Keys;
 import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
 import com.gitblit.utils.DiffUtils.DiffOutputType;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.StringUtils;
@@ -29,6 +33,7 @@
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
+import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
 
 @CacheControl(LastModified.BOOT)
@@ -39,20 +44,34 @@
 
 		final String blobPath = WicketUtils.getPath(params);
 		final String baseObjectId = WicketUtils.getBaseObjectId(params);
+		final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
 
 		Repository r = getRepository();
 		RevCommit commit = getCommit();
 
+		final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+
 		String diff;
 		if (StringUtils.isEmpty(baseObjectId)) {
 			// use first parent
-			diff = DiffUtils.getDiff(r, commit, blobPath, DiffOutputType.HTML).content;
+			RevCommit parent = commit.getParentCount() == 0 ? null : commit.getParent(0);
+			ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+					parent.getName(), commit.getName(), imageExtensions);
+			diff = DiffUtils.getDiff(r, commit, blobPath, diffComparator, DiffOutputType.HTML, handler, 3).content;
+			if (handler.getImgDiffCount() > 0) {
+				addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+			}
 			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
 					WicketUtils.newPathParameter(repositoryName, objectId, blobPath)));
 		} else {
 			// base commit specified
 			RevCommit baseCommit = JGitUtils.getCommit(r, baseObjectId);
-			diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, DiffOutputType.HTML).content;
+			ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+					baseCommit.getName(), commit.getName(), imageExtensions);
+			diff = DiffUtils.getDiff(r, baseCommit, commit, blobPath, diffComparator, DiffOutputType.HTML, handler, 3).content;
+			if (handler.getImgDiffCount() > 0) {
+				addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+			}
 			add(new BookmarkablePageLink<Void>("patchLink", PatchPage.class,
 					WicketUtils.newBlobDiffParameter(repositoryName, baseObjectId, objectId,
 							blobPath)));
@@ -62,6 +81,8 @@
 				WicketUtils.newObjectParameter(repositoryName, objectId)));
 		add(new BookmarkablePageLink<Void>("commitDiffLink", CommitDiffPage.class,
 				WicketUtils.newObjectParameter(repositoryName, objectId)));
+		add(new LinkPanel("whitespaceLink", null, getString(diffComparator.getOpposite().getTranslationKey()),
+				BlobDiffPage.class, WicketUtils.newDiffParameter(repositoryName, objectId, diffComparator.getOpposite(), blobPath)));
 
 		// diff page links
 		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.html b/src/main/java/com/gitblit/wicket/pages/BlobPage.html
index 3f9a959..289c149 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.html
@@ -40,9 +40,8 @@
   </wicket:link>
 </wicket:head>
 
+<body>
 <wicket:extend>
-<!-- need to specify body.onload -->
-<body onload="prettyPrint()">
 
 		<!-- blob nav links -->	
 		<div class="page_nav2">
@@ -61,6 +60,6 @@
 		<!--  blob image -->
 		<img wicket:id="blobImage" style="padding-top:5px;"></img>
 	
-</body>
 </wicket:extend>
+</body>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
index 3c244f9..1ef8f22 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -137,6 +137,7 @@
 						table = missingBlob(blobPath, commit);
 					} else {
 						table = generateSourceView(source, extension, type == 1);
+						addBottomScriptInline("jQuery(prettyPrint);");
 					}
 					add(new Label("blobText", table).setEscapeModelStrings(false));
 					add(new Image("blobImage").setVisible(false));
@@ -150,6 +151,7 @@
 					table = missingBlob(blobPath, commit);
 				} else {
 					table = generateSourceView(source, null, false);
+					addBottomScriptInline("jQuery(prettyPrint);");
 				}
 				add(new Label("blobText", table).setEscapeModelStrings(false));
 				add(new Image("blobImage").setVisible(false));
@@ -193,7 +195,8 @@
 		} else {
 			sb.append("<pre class=\"plainprint\">");
 		}
-		lines = StringUtils.escapeForHtml(source, true).split("\n");
+		final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+		lines = StringUtils.escapeForHtml(source, true, tabLength).split("\n");
 
 		sb.append("<table width=\"100%\"><tbody>");
 
diff --git a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
index a6aca22..259a4bf 100644
--- a/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
@@ -19,7 +19,6 @@
 
 import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.markup.html.form.Button;
-import org.apache.wicket.markup.html.form.PasswordTextField;
 import org.apache.wicket.markup.html.form.StatelessForm;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
@@ -31,6 +30,7 @@
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
 
 public class ChangePasswordPage extends RootSubPage {
 
@@ -114,10 +114,10 @@
 				setResponsePage(RepositoriesPage.class);
 			}
 		};
-		PasswordTextField passwordField = new PasswordTextField("password", password);
+		NonTrimmedPasswordTextField passwordField = new NonTrimmedPasswordTextField("password", password);
 		passwordField.setResetPassword(false);
 		form.add(passwordField);
-		PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+		NonTrimmedPasswordTextField confirmPasswordField = new NonTrimmedPasswordTextField("confirmPassword",
 				confirmPassword);
 		confirmPasswordField.setResetPassword(false);
 		form.add(confirmPasswordField);
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
index 2c35a28..2e0d57c 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.html
@@ -9,7 +9,7 @@
 
 	<!-- commitdiff nav links -->	
 	<div class="page_nav2">
-		<wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a>
+		<wicket:message key="gb.parent"></wicket:message>: <span wicket:id="parentLink">[parent link]</span> | <a wicket:id="patchLink"><wicket:message key="gb.patch"></wicket:message></a> | <a wicket:id="commitLink"><wicket:message key="gb.commit"></wicket:message></a> | <a wicket:id="whitespaceLink"></a>
 	</div>	
 	
 	<!-- commit header -->
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
index d827c44..b56d721 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -31,11 +31,13 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.Constants;
+import com.gitblit.Keys;
 import com.gitblit.models.GitNote;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.SubmoduleModel;
 import com.gitblit.servlet.RawServlet;
 import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
 import com.gitblit.utils.DiffUtils.DiffOutput;
 import com.gitblit.utils.DiffUtils.DiffOutputType;
 import com.gitblit.utils.JGitUtils;
@@ -45,7 +47,7 @@
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.CommitLegendPanel;
 import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.RefsPanel;
 
@@ -55,11 +57,9 @@
 	public CommitDiffPage(PageParameters params) {
 		super(params);
 
-		Repository r = getRepository();
-
-		RevCommit commit = getCommit();
-
-		final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, DiffOutputType.HTML);
+		final Repository r = getRepository();
+		final RevCommit commit = getCommit();
+		final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
 
 		List<String> parents = new ArrayList<String>();
 		if (commit.getParentCount() > 0) {
@@ -79,8 +79,19 @@
 				WicketUtils.newObjectParameter(repositoryName, objectId)));
 		add(new BookmarkablePageLink<Void>("commitLink", CommitPage.class,
 				WicketUtils.newObjectParameter(repositoryName, objectId)));
+		add(new LinkPanel("whitespaceLink", null, getString(diffComparator.getOpposite().getTranslationKey()),
+				CommitDiffPage.class, WicketUtils.newDiffParameter(repositoryName, objectId, diffComparator.getOpposite())));
 
 		add(new CommitHeaderPanel("commitHeader", repositoryName, commit));
+
+		final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+		final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+				parents.isEmpty() ? null : parents.get(0), commit.getName(), imageExtensions);
+		final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+		final DiffOutput diff = DiffUtils.getCommitDiff(r, commit, diffComparator, DiffOutputType.HTML, handler, tabLength);
+		if (handler.getImgDiffCount() > 0) {
+			addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+		}
 
 		// add commit diffstat
 		int insertions = 0;
@@ -105,7 +116,7 @@
 				item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
 				item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
 						Constants.SearchType.AUTHOR));
-				item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
+				item.add(new AvatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
 				item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
 						.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
 				item.add(new Label("noteContent", bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName,
@@ -145,10 +156,10 @@
 					hasSubmodule = submodule.hasSubmodule;
 
 					// add relative link
-					item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
+					item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId));
 				} else {
 					// add relative link
-					item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
+					item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId));
 				}
 
 				// quick links
diff --git a/src/main/java/com/gitblit/wicket/pages/CommitPage.java b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
index 072bb20..0a1a68d 100644
--- a/src/main/java/com/gitblit/wicket/pages/CommitPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/CommitPage.java
@@ -44,7 +44,7 @@
 import com.gitblit.wicket.panels.CommitLegendPanel;
 import com.gitblit.wicket.panels.CompressedDownloadsPanel;
 import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.RefsPanel;
 
@@ -133,7 +133,7 @@
 				item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
 				item.add(createPersonPanel("authorName", entry.notesRef.getAuthorIdent(),
 						Constants.SearchType.AUTHOR));
-				item.add(new GravatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
+				item.add(new AvatarImage("noteAuthorAvatar", entry.notesRef.getAuthorIdent()));
 				item.add(WicketUtils.createTimestampLabel("authorDate", entry.notesRef
 						.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
 				item.add(new Label("noteContent", bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName,
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.html b/src/main/java/com/gitblit/wicket/pages/ComparePage.html
index d7df717..74c9831 100644
--- a/src/main/java/com/gitblit/wicket/pages/ComparePage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.html
@@ -18,6 +18,7 @@
 					<select wicket:id="fromRef" class="span3" />
 					<i class="icon-arrow-right"></i>
 					<select wicket:id="toRef" class="span3" />
+                      <label style="padding:0px 5px;" class="checkbox"><input type="checkbox" wicket:id="ignoreWhitespaceCheckbox" /> <span wicket:id="ignoreWhitespaceLabel"></span></label>
 					<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
 				</form>
 			</div>
@@ -26,6 +27,7 @@
 					<input wicket:id="fromId" type="text" class="span3" />
 					<i class="icon-arrow-right"></i>
 					<input wicket:id="toId" type="text" class="span3" />
+                    <label style="padding:0px 5px;" class="checkbox"><input type="checkbox" wicket:id="ignoreWhitespaceCheckbox" /> <span wicket:id="ignoreWhitespaceLabel"></span></label>
 					<button class="btn" type="submit"><wicket:message key="gb.compare"></wicket:message></button>
 				</form>
 			</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/ComparePage.java b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
index 1ec6613..7e7ac2f 100644
--- a/src/main/java/com/gitblit/wicket/pages/ComparePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ComparePage.java
@@ -21,6 +21,7 @@
 
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.CheckBox;
 import org.apache.wicket.markup.html.form.DropDownChoice;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
@@ -37,12 +38,14 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 
+import com.gitblit.Keys;
 import com.gitblit.models.PathModel.PathChangeModel;
 import com.gitblit.models.RefModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.SubmoduleModel;
 import com.gitblit.servlet.RawServlet;
 import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
 import com.gitblit.utils.DiffUtils.DiffOutput;
 import com.gitblit.utils.DiffUtils.DiffOutputType;
 import com.gitblit.utils.JGitUtils;
@@ -67,6 +70,8 @@
 
 	IModel<String> fromRefId = new Model<String>("");
 	IModel<String> toRefId = new Model<String>("");
+
+	IModel<Boolean> ignoreWhitespace = Model.of(true);
 
 	public ComparePage(PageParameters params) {
 		super(params);
@@ -111,7 +116,15 @@
 			fromCommitId.setObject(startId);
 			toCommitId.setObject(endId);
 
-			final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, DiffOutputType.HTML);
+			final List<String> imageExtensions = app().settings().getStrings(Keys.web.imageExtensions);
+			final ImageDiffHandler handler = new ImageDiffHandler(this, repositoryName,
+					fromCommit.getName(), toCommit.getName(), imageExtensions);
+			final DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
+			final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
+			final DiffOutput diff = DiffUtils.getDiff(r, fromCommit, toCommit, diffComparator, DiffOutputType.HTML, handler, tabLength);
+			if (handler.getImgDiffCount() > 0) {
+				addBottomScript("scripts/imgdiff.js"); // Tiny support script for image diffs
+			}
 
 			// add compare diffstat
 			int insertions = 0;
@@ -160,10 +173,10 @@
 						hasSubmodule = submodule.hasSubmodule;
 
 						// add relative link
-						item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#" + entry.path));
+						item.add(new LinkPanel("pathName", "list", entry.path + " @ " + getShortObjectId(submoduleId), "#n" + entry.objectId));
 					} else {
 						// add relative link
-						item.add(new LinkPanel("pathName", "list", entry.path, "#" + entry.path));
+						item.add(new LinkPanel("pathName", "list", entry.path, "#n" + entry.objectId));
 					}
 
 					// quick links
@@ -204,6 +217,10 @@
 			comparison.add(new Label("diffText", diff.content).setEscapeModelStrings(false));
 		}
 
+		// set the default DiffComparator
+		DiffComparator diffComparator = WicketUtils.getDiffComparator(params);
+		ignoreWhitespace.setObject(DiffComparator.IGNORE_WHITESPACE == diffComparator);
+
 		//
 		// ref selection form
 		//
@@ -215,8 +232,13 @@
 			public void onSubmit() {
 				String from = ComparePage.this.fromRefId.getObject();
 				String to = ComparePage.this.toRefId.getObject();
+				boolean ignoreWS = ignoreWhitespace.getObject();
 
 				PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
+				if (ignoreWS) {
+					params.put("w", 1);
+				}
+
 				String relativeUrl = urlFor(ComparePage.class, params).toString();
 				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
 				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
@@ -237,6 +259,8 @@
 		}
 		refsForm.add(new DropDownChoice<String>("fromRef", fromRefId, refs).setEnabled(refs.size() > 0));
 		refsForm.add(new DropDownChoice<String>("toRef", toRefId, refs).setEnabled(refs.size() > 0));
+		refsForm.add(new Label("ignoreWhitespaceLabel", getString(DiffComparator.IGNORE_WHITESPACE.getTranslationKey())));
+		refsForm.add(new CheckBox("ignoreWhitespaceCheckbox", ignoreWhitespace));
 		add(refsForm);
 
 		//
@@ -250,8 +274,12 @@
 			public void onSubmit() {
 				String from = ComparePage.this.fromCommitId.getObject();
 				String to = ComparePage.this.toCommitId.getObject();
+				boolean ignoreWS = ignoreWhitespace.getObject();
 
 				PageParameters params = WicketUtils.newRangeParameter(repositoryName, from, to);
+				if (ignoreWS) {
+					params.put("w", 1);
+				}
 				String relativeUrl = urlFor(ComparePage.class, params).toString();
 				String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
 				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
@@ -265,6 +293,8 @@
 		TextField<String> toIdField = new TextField<String>("toId", toCommitId);
 		WicketUtils.setInputPlaceholder(toIdField, getString("gb.to") + "...");
 		idsForm.add(toIdField);
+		idsForm.add(new Label("ignoreWhitespaceLabel", getString(DiffComparator.IGNORE_WHITESPACE.getTranslationKey())));
+		idsForm.add(new CheckBox("ignoreWhitespaceCheckbox", ignoreWhitespace));
 		add(idsForm);
 
 		r.close();
diff --git a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
index 1e683b4..7a55b9f 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.html
@@ -20,6 +20,7 @@
 		<li><a href="#search" data-toggle="tab"><wicket:message key="gb.search"></wicket:message></a></li>
 		<li><a href="#gc" data-toggle="tab"><wicket:message key="gb.gc"></wicket:message></a></li>
 		<li><a href="#misc" data-toggle="tab"><wicket:message key="gb.miscellaneous"></wicket:message></a></li>
+        <li><a href="#admin" data-toggle="tab"><wicket:message key="gb.administration"></wicket:message></a></li>
 	</ul>
 
 	<!-- tab content -->
@@ -178,8 +179,16 @@
 			<div wicket:id="mailingLists"></div>
 		
 		</div>
-		
-		<div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /> &nbsp; <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" /></div>
+
+        <!-- administration -->
+        <div class="tab-pane" id="admin">
+        
+            <h4><wicket:message key="gb.deleteRepositoryHeader"></wicket:message></h4>
+            <p><wicket:message key="gb.deleteRepositoryDescription"></wicket:message></p>
+            <input class="btn btn-danger" type="submit" value="Delete" wicket:message="value:gb.delete" wicket:id="delete" />
+		</div>
+    
+		<div class="form-actions"><input class="btn btn-appmenu" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" /></div>
 		
 		</div>
 	</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
index f537f33..a43d8db 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditTeamPage.java
@@ -39,6 +39,7 @@
 import org.apache.wicket.model.util.ListModel;
 
 import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Role;
 import com.gitblit.GitBlitException;
 import com.gitblit.Keys;
 import com.gitblit.models.RegistrantAccessPermission;
@@ -221,14 +222,23 @@
 		// do not let the browser pre-populate these fields
 		form.add(new SimpleAttributeModifier("autocomplete", "off"));
 
-		// not all user services support manipulating team memberships
+		// not all user providers support manipulating team memberships
 		boolean editMemberships = app().authentication().supportsTeamMembershipChanges(teamModel);
+
+		// not all user providers support manipulating the admin role
+		boolean changeAdminRole = app().authentication().supportsRoleChanges(teamModel, Role.ADMIN);
+
+		// not all user providers support manipulating the create role
+		boolean changeCreateRole = app().authentication().supportsRoleChanges(teamModel, Role.CREATE);
+
+		// not all user providers support manipulating the fork role
+		boolean changeForkRole = app().authentication().supportsRoleChanges(teamModel, Role.FORK);
 
 		// field names reflective match TeamModel fields
 		form.add(new TextField<String>("name"));
-		form.add(new CheckBox("canAdmin"));
-		form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true)));
-		form.add(new CheckBox("canCreate"));
+		form.add(new CheckBox("canAdmin").setEnabled(changeAdminRole));
+		form.add(new CheckBox("canFork").setEnabled(app().settings().getBoolean(Keys.web.allowForking, true) && changeForkRole));
+		form.add(new CheckBox("canCreate").setEnabled(changeCreateRole));
 		form.add(users.setEnabled(editMemberships));
 		mailingLists = new Model<String>(teamModel.mailingLists == null ? ""
 				: StringUtils.flattenStrings(teamModel.mailingLists, " "));
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
index b5fe0ae..b12d0c7 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.html
@@ -39,6 +39,8 @@
 				</div>
 			</td></tr>
 			<tr wicket:id="status"></tr>
+            <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
+            <tr wicket:id="priority"></tr>
 			<tr wicket:id="responsible"></tr>
 			<tr wicket:id="milestone"></tr>
 			<tr wicket:id="mergeto"></tr>
@@ -71,5 +73,9 @@
 	<th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="mergeto"></select></td>
 </wicket:fragment>
 
+<wicket:fragment wicket:id="priorityFragment">
+    <th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td>
+</wicket:fragment>
+
 </wicket:extend>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
index c3d405b..192b48c 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditTicketPage.java
@@ -84,6 +84,10 @@
 
 	private Label descriptionPreview;
 
+	private IModel<TicketModel.Priority> priorityModel;
+
+	private IModel<TicketModel.Severity> severityModel;
+
 	public EditTicketPage(PageParameters params) {
 		super(params);
 
@@ -117,6 +121,8 @@
 		milestoneModel = Model.of();
 		mergeToModel = Model.of(ticket.mergeTo == null ? getRepositoryModel().mergeTo : ticket.mergeTo);
 		statusModel = Model.of(ticket.status);
+		priorityModel = Model.of(ticket.priority);
+		severityModel = Model.of(ticket.severity);
 
 		setStatelessHint(false);
 		setOutputMarkupId(true);
@@ -160,6 +166,9 @@
 		Fragment status = new Fragment("status", "statusFragment", this);
 		status.add(new DropDownChoice<TicketModel.Status>("status", statusModel, statusChoices));
 		form.add(status);
+
+		List<TicketModel.Severity> severityChoices = Arrays.asList(TicketModel.Severity.choices());
+		form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, severityChoices));
 
 		if (currentUser.canAdmin(ticket, getRepositoryModel())) {
 			// responsible
@@ -214,10 +223,16 @@
 				milestones.add(new TicketMilestone(NIL));
 			}
 
+			// milestone
 			Fragment milestone = new Fragment("milestone", "milestoneFragment", this);
-
 			milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
 			form.add(milestone.setVisible(!milestones.isEmpty()));
+
+			// priority
+			Fragment priority = new Fragment("priority", "priorityFragment", this);
+			List<TicketModel.Priority> priorityChoices = Arrays.asList(TicketModel.Priority.choices());
+			priority.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, priorityChoices));
+			form.add(priority);
 
 			// mergeTo (integration branch)
 			List<String> branches = new ArrayList<String>();
@@ -238,6 +253,7 @@
 			form.add(new Label("responsible").setVisible(false));
 			form.add(new Label("milestone").setVisible(false));
 			form.add(new Label("mergeto").setVisible(false));
+			form.add(new Label("priority").setVisible(false));
 		}
 
 		form.add(new AjaxButton("update") {
@@ -316,6 +332,18 @@
 					}
 				}
 
+				TicketModel.Priority priority = priorityModel.getObject();
+				if (!ticket.priority.equals(priority))
+				{
+					change.setField(Field.priority, priority);
+				}
+
+				TicketModel.Severity severity = severityModel.getObject();
+				if (!ticket.severity.equals(severity))
+				{
+					change.setField(Field.severity, severity);
+				}
+
 				String mergeTo = mergeToModel.getObject();
 				if ((StringUtils.isEmpty(ticket.mergeTo) && !StringUtils.isEmpty(mergeTo))
 						|| (!StringUtils.isEmpty(mergeTo) && !mergeTo.equals(ticket.mergeTo))) {
diff --git a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
index 454aa61..220bee3 100644
--- a/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EditUserPage.java
@@ -27,7 +27,6 @@
 import org.apache.wicket.markup.html.form.Button;
 import org.apache.wicket.markup.html.form.CheckBox;
 import org.apache.wicket.markup.html.form.Form;
-import org.apache.wicket.markup.html.form.PasswordTextField;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.model.CompoundPropertyModel;
 import org.apache.wicket.model.Model;
@@ -35,12 +34,14 @@
 import org.apache.wicket.model.util.ListModel;
 
 import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Constants.Role;
 import com.gitblit.GitBlitException;
 import com.gitblit.Keys;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
 import com.gitblit.wicket.RequiresAdminRole;
 import com.gitblit.wicket.StringChoiceRenderer;
 import com.gitblit.wicket.WicketUtils;
@@ -177,7 +178,9 @@
 
 				// update user permissions
 				for (RegistrantAccessPermission repositoryPermission : permissions) {
-					userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+					if (repositoryPermission.mutable) {
+						userModel.setRepositoryPermission(repositoryPermission.registrant, repositoryPermission.permission);
+					}
 				}
 
 				Iterator<String> selectedTeams = teams.getSelectedChoices();
@@ -216,24 +219,33 @@
 		// do not let the browser pre-populate these fields
 		form.add(new SimpleAttributeModifier("autocomplete", "off"));
 
-		// not all user services support manipulating username and password
+		// not all user providers support manipulating username and password
 		boolean editCredentials = app().authentication().supportsCredentialChanges(userModel);
 
-		// not all user services support manipulating display name
+		// not all user providers support manipulating display name
 		boolean editDisplayName = app().authentication().supportsDisplayNameChanges(userModel);
 
-		// not all user services support manipulating email address
+		// not all user providers support manipulating email address
 		boolean editEmailAddress = app().authentication().supportsEmailAddressChanges(userModel);
 
-		// not all user services support manipulating team memberships
+		// not all user providers support manipulating team memberships
 		boolean editTeams = app().authentication().supportsTeamMembershipChanges(userModel);
+
+		// not all user providers support manipulating the admin role
+		boolean changeAdminRole = app().authentication().supportsRoleChanges(userModel, Role.ADMIN);
+
+		// not all user providers support manipulating the create role
+		boolean changeCreateRole = app().authentication().supportsRoleChanges(userModel, Role.CREATE);
+
+		// not all user providers support manipulating the fork role
+		boolean changeForkRole = app().authentication().supportsRoleChanges(userModel, Role.FORK);
 
 		// field names reflective match UserModel fields
 		form.add(new TextField<String>("username").setEnabled(editCredentials));
-		PasswordTextField passwordField = new PasswordTextField("password");
+		NonTrimmedPasswordTextField passwordField = new NonTrimmedPasswordTextField("password");
 		passwordField.setResetPassword(false);
 		form.add(passwordField.setEnabled(editCredentials));
-		PasswordTextField confirmPasswordField = new PasswordTextField("confirmPassword",
+		NonTrimmedPasswordTextField confirmPasswordField = new NonTrimmedPasswordTextField("confirmPassword",
 				confirmPassword);
 		confirmPasswordField.setResetPassword(false);
 		form.add(confirmPasswordField.setEnabled(editCredentials));
@@ -245,7 +257,7 @@
 			// display a disabled-yet-checked checkbox
 			form.add(new CheckBox("canAdmin", Model.of(true)).setEnabled(false));
 		} else {
-			form.add(new CheckBox("canAdmin"));
+			form.add(new CheckBox("canAdmin").setEnabled(changeAdminRole));
 		}
 
 		if (userModel.canFork() && !userModel.canFork) {
@@ -254,7 +266,7 @@
 			form.add(new CheckBox("canFork", Model.of(true)).setEnabled(false));
 		} else {
 			final boolean forkingAllowed = app().settings().getBoolean(Keys.web.allowForking, true);
-			form.add(new CheckBox("canFork").setEnabled(forkingAllowed));
+			form.add(new CheckBox("canFork").setEnabled(forkingAllowed && changeForkRole));
 		}
 
 		if (userModel.canCreate() && !userModel.canCreate) {
@@ -262,7 +274,7 @@
 			// display a disabled-yet-checked checkbox
 			form.add(new CheckBox("canCreate", Model.of(true)).setEnabled(false));
 		} else {
-			form.add(new CheckBox("canCreate"));
+			form.add(new CheckBox("canCreate").setEnabled(changeCreateRole));
 		}
 
 		form.add(new CheckBox("excludeFromFederation"));
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
index b3c5243..72d1e1a 100644
--- a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
@@ -55,7 +55,7 @@
 		}
 
 		HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
-		List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
+		List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
 		RepositoryUrl primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
 		String url = primaryUrl != null ? primaryUrl.url : "";
 
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestorePage.html b/src/main/java/com/gitblit/wicket/pages/FilestorePage.html
new file mode 100644
index 0000000..e373e70
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestorePage.html
@@ -0,0 +1,37 @@
+<!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"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+	
+	<div class="markdown" style="padding: 10px 0px 5px 0px;">
+		<span wicket:id="repositoriesMessage">[repositories message]</span>
+		<span style="float:right"><a href="#" wicket:id="filestoreHelp"><span wicket:id="helpMessage">[help message]</span></a></span>
+	</div>
+	
+	<table class="repositories">
+		<tr>
+			<th><wicket:message key="gb.status">[Object status]</wicket:message></th>
+			<th><wicket:message key="gb.statusChangedOn">[changedOn]</wicket:message></th>
+			<th><wicket:message key="gb.statusChangedBy">[changedBy]</wicket:message></th>
+			<th><wicket:message key="gb.oid">[Object ID]</wicket:message></th>
+			<th><wicket:message key="gb.size">[file size]</wicket:message></th>
+		</tr>
+		<tbody>		
+       		<tr wicket:id="fileRow">
+       			<td><center><span class="list" wicket:id="status">[Object state]</span></center></td>
+       			<td><span class="list" wicket:id="on">[changedOn]</span></td>
+       			<td><span class="list" wicket:id="by">[changedBy]</span></td>
+       			<td class="sha256"><span class="list" wicket:id="oid">[Object ID]</span></td>
+       			<td><span class="list" wicket:id="size">[file size]</span></td>
+       		</tr>
+    	</tbody>
+	</table>
+</div>
+</wicket:extend>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestorePage.java b/src/main/java/com/gitblit/wicket/pages/FilestorePage.java
new file mode 100644
index 0000000..97d5f25
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestorePage.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 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.pages;
+
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.wicket.Component;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.Constants;
+import com.gitblit.models.FilestoreModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.wicket.FilestoreUI;
+import com.gitblit.wicket.RequiresAdminRole;
+import com.gitblit.wicket.WicketUtils;
+
+/**
+ * Page to display the current status of the filestore.
+ * Certain errors also displayed to aid in fault finding
+ *
+ * @author Paul Martin
+ */
+@RequiresAdminRole
+public class FilestorePage extends RootPage {
+
+	public FilestorePage() {
+		super();
+		setupPage("", "");
+
+		final List<FilestoreModel> files = app().filestore().getAllObjects();
+		final long nBytesUsed = app().filestore().getFilestoreUsedByteCount();
+		final long nBytesAvailable = app().filestore().getFilestoreAvailableByteCount();
+
+		String message = MessageFormat.format(getString("gb.filestoreStats"), files.size(),
+				FileUtils.byteCountToDisplaySize(nBytesUsed), FileUtils.byteCountToDisplaySize(nBytesAvailable) );
+
+		Component repositoriesMessage = new Label("repositoriesMessage", message)
+				.setEscapeModelStrings(false).setVisible(message.length() > 0);
+
+		add(repositoriesMessage);
+
+		BookmarkablePageLink<Void> helpLink = new BookmarkablePageLink<Void>("filestoreHelp", FilestoreUsage.class);
+		helpLink.add(new Label("helpMessage", getString("gb.filestoreHelp")));
+		add(helpLink);
+
+
+		DataView<FilestoreModel> filesView = new DataView<FilestoreModel>("fileRow",
+				new ListDataProvider<FilestoreModel>(files)) {
+			private static final long serialVersionUID = 1L;
+			private int counter;
+
+			@Override
+			protected void onBeforeRender() {
+				super.onBeforeRender();
+				counter = 0;
+			}
+
+			@Override
+			public void populateItem(final Item<FilestoreModel> item) {
+				final FilestoreModel entry = item.getModelObject();
+
+				DateFormat dateFormater = new SimpleDateFormat(Constants.ISO8601);
+
+				UserModel user = app().users().getUserModel(entry.getChangedBy());
+				user = user == null ? UserModel.ANONYMOUS : user;
+
+				Label icon = FilestoreUI.getStatusIcon("status", entry);
+				item.add(icon);
+				item.add(new Label("on", dateFormater.format(entry.getChangedOn())));
+				item.add(new Label("by", user.getDisplayName()));
+
+				item.add(new Label("oid", entry.oid));
+				item.add(new Label("size", FileUtils.byteCountToDisplaySize(entry.getSize())));
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+
+		};
+
+		add(filesView);
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html
new file mode 100644
index 0000000..e9bff47
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.html
@@ -0,0 +1,69 @@
+<!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"> 
+
+<body>
+<wicket:extend>
+<div class="container">
+<div class="markdown">
+<div class="row">
+<div class="span10 offset1">	
+	
+	<div class="alert alert-danger">
+	<h3><center>Using the Filestore</center></h3>
+	<p>
+		<strong>All clients intending to use the filestore must first install the <a href="https://git-lfs.github.com/">Git-LFS Client</a> and then run <code>git lfs init</code> to register the hooks globally.</strong><br/>
+		<i>This version of GitBlit has been verified with Git-LFS client version 0.6.0 which requires Git v1.8.2 or higher.</i>
+	</p>
+	</div>
+		
+	<h3>Clone</h3>
+	<p>
+	Just <code>git clone</code> as usual, no further action is required as GitBlit is configured to use the default Git-LFS end point <code>{repository}/info/lfs/objects/</code>.<br/>
+	<i>If the repository uses a 3rd party Git-LFS server you will need to <a href="https://github.com/github/git-lfs/blob/master/docs/spec.md#the-server">manually configure the correct endpoints</a></i>.
+	</p>
+	
+	<h3>Add</h3>
+	<p>After configuring the file types or paths to be tracked using <code>git lfs track "*.bin"</code> just add files as usual with <code>git add</code> command.<br/>
+	<i>Tracked files can also be configured manually using the <code>.gitattributes</code> file</i>.</p>
+	
+	<h3>Remove</h3>
+	<p>When you remove a Git-LFS tracked file only the pointer file will be removed from your repository.<br/>
+	<i>All files remain on the server to allow previous versions to be checked out.</i>
+	</p>
+	
+	<h3>Learn more...</h3>
+	<p><a href="https://github.com/github/git-lfs/blob/master/docs/spec.md">See the current Git-LFS specification for further details</a>.</p>
+	<br />
+	
+	<div class="alert alert-warn">
+	<h3><center>Limitations & Warnings</center></h3>
+	<p>GitBlit currently provides a server-only implementation of the opensource Git-LFS API, <a href="https://github.com/github/git-lfs/wiki/Implementations">other implementations</a> are available.<br/>
+	However, until <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=470333">JGit provides Git-LFS client capabilities</a> some GitBlit features may not be fully supported when using the filestore.
+	Notably:
+		<ul>
+		<li>Mirroring a repository that uses Git-LFS - Only the pointer files, not the large files, are mirrored.</li>
+		<li>Federation -  Only the pointer files, not the large files, are transfered.</li>
+		</ul>
+	</p>
+	</div>
+	
+	<div class="alert alert-info">
+	<h3><center>GitBlit Configuration</center></h3>
+	<p>GitBlit provides the following configuration items when using the filestore:
+		<h4>filestore.storageFolder</h4>
+		<p>Defines the path on the server where filestore objects are to be saved. This defaults to <code>${baseFolder}/lfs</code></p>
+		<h4>filestore.maxUploadSize</h4>
+		<p>Defines the maximum allowable size that can be uploaded to the filestore.  Once a file is uploaded it will be unaffected by later changes in this property. This defaults to <code>-1</code> indicating no limits.</p>
+	</p>
+	</div>
+	
+</div>
+</div>
+</div>
+</div>
+</wicket:extend>	
+</body>
+</html>
diff --git a/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java
new file mode 100644
index 0000000..9bd8e55
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/FilestoreUsage.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2015 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.pages;
+
+public class FilestoreUsage extends RootSubPage {
+
+	public FilestoreUsage() {
+		super();
+		setupPage("", "");
+	}
+
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/ForksPage.java b/src/main/java/com/gitblit/wicket/pages/ForksPage.java
index 9fd7f4d..045f5f7 100644
--- a/src/main/java/com/gitblit/wicket/pages/ForksPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ForksPage.java
@@ -34,7 +34,7 @@
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 
 public class ForksPage extends RepositoryPage {
@@ -63,7 +63,7 @@
 						user = new UserModel(repository.projectPath.substring(1));
 					}
 					PersonIdent ident = new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.getDisplayName() : user.emailAddress);
-					item.add(new GravatarImage("anAvatar", ident, 20));
+					item.add(new AvatarImage("anAvatar", ident, 20));
 					if (pageRepository.equals(repository)) {
 						// do not link to self
 						item.add(new Label("aProject", user.getDisplayName()));
@@ -136,6 +136,9 @@
 
 	protected List<FlatFork> flatten(ForkModel node, int level) {
 		List<FlatFork> list = new ArrayList<FlatFork>();
+		if (node == null) {
+			return list;
+		}
 		list.add(new FlatFork(node.repository, level));
 		if (!node.isLeaf()) {
 			for (ForkModel fork : node.forks) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
new file mode 100644
index 0000000..dc0c5ae
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/ImageDiffHandler.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2014 Tom <tw201207@gmail.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.pages;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.apache.wicket.protocol.http.WebApplication;
+import org.apache.wicket.protocol.http.WicketURLEncoder;
+import org.eclipse.jgit.diff.DiffEntry;
+import org.eclipse.jgit.diff.DiffEntry.Side;
+import org.jsoup.nodes.Element;
+
+import com.gitblit.servlet.RawServlet;
+import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.HtmlBuilder;
+
+/**
+ * A {@link DiffUtils.BinaryDiffHandler BinaryDiffHandler} for images.
+ *
+ * @author Tom <tw201207@gmail.com>
+ */
+public class ImageDiffHandler implements DiffUtils.BinaryDiffHandler {
+
+	private final String oldCommitId;
+	private final String newCommitId;
+	private final String repositoryName;
+	private final BasePage page;
+	private final List<String> imageExtensions;
+
+	private int imgDiffCount = 0;
+
+	public ImageDiffHandler(final BasePage page, final String repositoryName, final String oldCommitId, final String newCommitId,
+			final List<String> imageExtensions) {
+		this.page = page;
+		this.repositoryName = repositoryName;
+		this.oldCommitId = oldCommitId;
+		this.newCommitId = newCommitId;
+		this.imageExtensions = imageExtensions;
+	}
+
+	/** {@inheritDoc} */
+	@Override
+	public String renderBinaryDiff(DiffEntry diffEntry) {
+		switch (diffEntry.getChangeType()) {
+		case MODIFY:
+		case RENAME:
+		case COPY:
+			// TODO: for very small images such as icons, the slider doesn't really help. Two possible
+			// approaches: either upscale them for display (may show blurry upscaled images), or show
+			// them side by side (may still be too small to really make out the differences).
+			String oldUrl = getImageUrl(diffEntry, Side.OLD);
+			String newUrl = getImageUrl(diffEntry, Side.NEW);
+			if (oldUrl != null && newUrl != null) {
+				imgDiffCount++;
+				String id = "imgdiff" + imgDiffCount;
+				HtmlBuilder builder = new HtmlBuilder("div");
+				Element wrapper = builder.root().attr("class", "imgdiff-container").attr("id", "imgdiff-" + id);
+				Element container = wrapper.appendElement("div").attr("class", "imgdiff-ovr-slider").appendElement("div").attr("class", "imgdiff");
+				Element old = container.appendElement("div").attr("class", "imgdiff-left");
+				// style='max-width:640px;' is necessary for ensuring that the browser limits large images
+				// to some reasonable width, and to override the "img { max-width: 100%; }" from bootstrap.css,
+				// which would scale the left image to the width of its resizeable container, which isn't what
+				// we want here. Note that the max-width must be defined directly as inline style on the element,
+				// otherwise browsers ignore it if the image is larger, and we end up with an image display that
+				// is too wide.
+				// XXX: Maybe add a max-height, too, to limit portrait-oriented images to some reasonable height?
+				// (Like a 300x10000px image...)
+				old.appendElement("img").attr("class", "imgdiff-old").attr("id", id).attr("style", "max-width:640px;").attr("src", oldUrl);
+				container.appendElement("img").attr("class", "imgdiff").attr("style", "max-width:640px;").attr("src", newUrl);
+				wrapper.appendElement("br");
+				Element controls = wrapper.appendElement("div");
+				// Opacity slider
+				controls.appendElement("div").attr("class", "imgdiff-opa-container").appendElement("a").attr("class", "imgdiff-opa-slider")
+						.attr("href", "#").attr("title", page.getString("gb.opacityAdjust"));
+				// Blink comparator: find Pluto!
+				controls.appendElement("a").attr("class", "imgdiff-link imgdiff-blink").attr("href", "#")
+						.attr("title", page.getString("gb.blinkComparator"))
+						.appendElement("img").attr("src", getStaticResourceUrl("blink32.png")).attr("width", "20");
+				// Pixel subtraction, initially not displayed, will be shown by imgdiff.js depending on feature test.
+				// (Uses CSS mix-blend-mode, which isn't supported on all browsers yet).
+				controls.appendElement("a").attr("class", "imgdiff-link imgdiff-subtract").attr("href", "#")
+						.attr("title", page.getString("gb.imgdiffSubtract")).attr("style", "display:none;")
+						.appendElement("img").attr("src", getStaticResourceUrl("sub32.png")).attr("width", "20");
+				return builder.toString();
+			}
+			break;
+		case ADD:
+			String url = getImageUrl(diffEntry, Side.NEW);
+			if (url != null) {
+				return new HtmlBuilder("img").root().attr("class", "diff-img").attr("src", url).toString();
+			}
+			break;
+		default:
+			break;
+		}
+		return null;
+	}
+
+	/** Returns the number of image diffs generated so far by this {@link ImageDiffHandler}. */
+	public int getImgDiffCount() {
+		return imgDiffCount;
+	}
+
+	/**
+	 * Constructs a URL that will fetch the designated resource in the git repository. The returned string will
+	 * contain the URL fully URL-escaped, but note that it may still contain unescaped ampersands, so the result
+	 * must still be run through HTML escaping if it is to be used in HTML.
+	 *
+	 * @return the URL to the image, if the given {@link DiffEntry} and {@link Side} refers to an image, or {@code null} otherwise.
+	 */
+	protected String getImageUrl(DiffEntry entry, Side side) {
+		String path = entry.getPath(side);
+		int i = path.lastIndexOf('.');
+		if (i > 0) {
+			String extension = path.substring(i + 1);
+			for (String ext : imageExtensions) {
+				if (ext.equalsIgnoreCase(extension)) {
+					String commitId = Side.NEW.equals(side) ? newCommitId : oldCommitId;
+					if (commitId != null) {
+						return RawServlet.asLink(page.getContextUrl(), urlencode(repositoryName), commitId, urlencode(path));
+					} else {
+						return null;
+					}
+				}
+			}
+		}
+		return null;
+	}
+
+	/**
+	 * Returns a URL that will fetch the designated static resource from within GitBlit.
+	 */
+	protected String getStaticResourceUrl(String contextRelativePath) {
+		return WebApplication.get().getRequestCycleProcessor().getRequestCodingStrategy().rewriteStaticRelativeUrl(contextRelativePath);
+	}
+
+	/**
+	 * Encode a URL component of a {@link RawServlet} URL in the special way that the servlet expects it. Note that
+	 * the %-encoding used does not encode '&amp;' or '&lt;'. Slashes are not encoded in the result.
+	 *
+	 * @param component
+	 *            to encode using %-encoding
+	 * @return the encoded component
+	 */
+	protected String urlencode(final String component) {
+		// RawServlet handles slashes itself. Note that only the PATH_INSTANCE fits the bill here: it encodes
+		// spaces as %20, and we just have to correct for encoded slashes. Java's standard URLEncoder would
+		// encode spaces as '+', and I don't know what effects that would have on other parts of GitBlit. It
+		// would also be wrong for path components (but fine for a query part), so we'd have to correct it, too.
+		//
+		// Actually, this should be done in RawServlet.asLink(). As it is now, this may be incorrect if that
+		// operation ever uses query parameters instead of paths, or if it is fixed to urlencode its path
+		// components. But I don't want to touch that static method in RawServlet.
+		return WicketURLEncoder.PATH_INSTANCE.encode(component, StandardCharsets.UTF_8.name()).replaceAll("%2[fF]", "/");
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
index d407783..ab3c0da 100644
--- a/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/LogoutPage.html
@@ -14,7 +14,7 @@
             			<span class="icon-bar"></span>
           		</a>
 				<a class="brand" wicket:id="rootLink">
-					<img src="gitblt_25_white.png" width="79" height="25" alt="gitblit" class="logo"/>
+					<img src="gitblt_25_white.png" width="79" alt="gitblit" class="logo"/>
 				</a>
 				
 			</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
index 91a6ef4..d62b7b2 100644
--- a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.html
@@ -24,6 +24,7 @@
 			<div class="span3">
 				<h3><wicket:message key="gb.repositories"></wicket:message></h3>
 				<select wicket:id="repositories" ></select>
+				<label><input type="checkbox" wicket:id="allrepos" /> <span><wicket:message key="gb.allRepositories"></wicket:message></span></label>
 			</div>
 			<div class="span9" style="margin-left:10px">
 				<div>
diff --git a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
index 4d4545a..b2fd903 100644
--- a/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java
@@ -17,11 +17,14 @@
 
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
 
 import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.CheckBox;
 import org.apache.wicket.markup.html.form.ListMultipleChoice;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.panel.Fragment;
@@ -66,6 +69,15 @@
 		int page = 1;
 		int pageSize = app().settings().getInteger(Keys.web.itemsPerPage, 50);
 
+		// display user-accessible selections
+		UserModel user = GitBlitWebSession.get().getUser();
+		List<String> availableRepositories = new ArrayList<String>();
+		for (RepositoryModel model : app().repositories().getRepositoryModels(user)) {
+			if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
+				availableRepositories.add(model.name);
+			}
+		}
+
 		if (params != null) {
 			String repository = WicketUtils.getRepositoryName(params);
 			if (!StringUtils.isEmpty(repository)) {
@@ -78,6 +90,10 @@
 				String value = params.getString("repositories", "");
 				List<String> list = StringUtils.getStringsFromValue(value);
 				repositories.addAll(list);
+			}
+
+			if (params.containsKey("allrepos")) {
+				repositories.addAll(availableRepositories);
 			}
 
 			if (params.containsKey("query")) {
@@ -96,14 +112,6 @@
 			}
 		}
 
-		// display user-accessible selections
-		UserModel user = GitBlitWebSession.get().getUser();
-		List<String> availableRepositories = new ArrayList<String>();
-		for (RepositoryModel model : app().repositories().getRepositoryModels(user)) {
-			if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
-				availableRepositories.add(model.name);
-			}
-		}
 		boolean luceneEnabled = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
 		if (luceneEnabled) {
 			if (availableRepositories.size() == 0) {
@@ -114,16 +122,18 @@
 		}
 
 		// enforce user-accessible repository selections
-		ArrayList<String> searchRepositories = new ArrayList<String>();
+		Set<String> uniqueRepositories = new LinkedHashSet<String>();
 		for (String selectedRepository : repositories) {
 			if (availableRepositories.contains(selectedRepository)) {
-				searchRepositories.add(selectedRepository);
+				uniqueRepositories.add(selectedRepository);
 			}
 		}
+		ArrayList<String> searchRepositories = new ArrayList<String>(uniqueRepositories);
 
 		// search form
 		final Model<String> queryModel = new Model<String>(query);
 		final Model<ArrayList<String>> repositoriesModel = new Model<ArrayList<String>>(searchRepositories);
+		final Model<Boolean> allreposModel = new Model<Boolean>(params != null && params.containsKey("allrepos"));
 		SessionlessForm<Void> form = new SessionlessForm<Void>("searchForm", getClass()) {
 
 			private static final long serialVersionUID = 1L;
@@ -135,13 +145,14 @@
 					error(getString("gb.undefinedQueryWarning"));
 					return;
 				}
-				if (repositoriesModel.getObject().size() == 0) {
+				if (repositoriesModel.getObject().size() == 0 && !allreposModel.getObject()) {
 					error(getString("gb.noSelectedRepositoriesWarning"));
 					return;
 				}
 				PageParameters params = new PageParameters();
 				params.put("repositories", StringUtils.flattenStrings(repositoriesModel.getObject()));
 				params.put("query", queryModel.getObject());
+				params.put("allrepos", allreposModel.getObject());
 				LuceneSearchPage page = new LuceneSearchPage(params);
 				setResponsePage(page);
 			}
@@ -152,6 +163,7 @@
 		selections.setMaxRows(8);
 		form.add(selections.setEnabled(luceneEnabled));
 		form.add(new TextField<String>("query", queryModel).setEnabled(luceneEnabled));
+		form.add(new CheckBox("allrepos", allreposModel));
 		add(form.setEnabled(luceneEnabled));
 
 		// execute search
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
index b0bc194..70869a1 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
@@ -25,18 +25,19 @@
 							<!--  query list -->						
 							<ul class="nav nav-list">
 								<li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li>
-								<li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
-								<li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
-								<li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
-								<li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
-								<li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+								<li><a wicket:id="changesQuery"><i class="fa fa-code-fork fa-fw"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
+								<li><a wicket:id="bugsQuery"><i class="fa fa-bug fa-fw"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
+								<li><a wicket:id="enhancementsQuery"><i class="fa fa-magic fa-fw"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
+								<li><a wicket:id="tasksQuery"><i class="fa fa-ticket fa-fw"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
+								<li><a wicket:id="questionsQuery"><i class="fa fa-question fa-fw"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+								<li><a wicket:id="maintenanceQuery"><i class="fa fa-cogs fa-fw"></i> <wicket:message key="gb.maintenanceTickets"></wicket:message></a></li>
 								<li wicket:id="userDivider" class="divider"></li>
-								<li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
-								<li><a wicket:id="responsibleQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li>
-								<li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
-								<li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
+								<li><a wicket:id="createdQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
+								<li><a wicket:id="responsibleQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourAssignedTickets"></wicket:message></a></li>
+								<li><a wicket:id="watchedQuery"><i class="fa fa-eye fa-fw"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
+								<li><a wicket:id="mentionsQuery"><i class="fa fa-comment fa-fw"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
 								<li class="divider"></li>
-								<li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
+								<li><a wicket:id="resetQuery"><i class="fa fa-bolt fa-fw"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
 							</ul>
 
 						</div>
@@ -62,6 +63,13 @@
 								</ul>
 							</div>
 							
+							<div class="btn-group">
+								<a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> <wicket:message key="gb.repository"></wicket:message>: <span style="font-weight:bold;" wicket:id="currentRepository"></span> <span class="caret"></span></a>
+								<ul class="dropdown-menu">
+									<li wicket:id="repository"><span wicket:id="repositoryLink"></span></li>
+								</ul>
+							</div>
+							
 							<div class="btn-group pull-right">
 								<div class="pagination pagination-right pagination-small">
 									<ul>
@@ -81,4 +89,4 @@
 				
 </wicket:extend>
 </body>
-</html>
\ No newline at end of file
+</html>
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
index c207d56..591c7fe 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
@@ -17,6 +17,10 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
 
 import org.apache.wicket.PageParameters;
@@ -27,6 +31,7 @@
 import org.apache.wicket.markup.repeater.data.ListDataProvider;
 
 import com.gitblit.Keys;
+import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TicketModel;
 import com.gitblit.models.TicketModel.Status;
 import com.gitblit.models.UserModel;
@@ -52,341 +57,437 @@
  */
 public class MyTicketsPage extends RootPage {
 
-	public MyTicketsPage() {
-		this(null);
-	}
+    public MyTicketsPage() {
+        this(null);
+    }
 
-	public MyTicketsPage(PageParameters params)	{
-		super(params);
-		setupPage("", getString("gb.myTickets"));
+    public MyTicketsPage(PageParameters params) {
+        super(params);
+        setupPage("", getString("gb.myTickets"));
 
-		UserModel currentUser = GitBlitWebSession.get().getUser();
-		if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
-			setRedirect(true);
-			setResponsePage(getApplication().getHomePage());
-			return;
-		}
+        UserModel currentUser = GitBlitWebSession.get().getUser();
+        if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
+            setRedirect(true);
+            setResponsePage(getApplication().getHomePage());
+            return;
+        }
 
-		final String username = currentUser.getName();
-		final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
-		final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
-		final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
-		final String queryParam = (params == null || StringUtils.isEmpty(params.getString("q", null))) ? "watchedby:" + username : params.getString("q", null);
-		final String searchParam = (params == null) ? "" : params.getString("s", null);
-		final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
-		final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
+        final String username = currentUser.getName();
 
-		// add the user title panel
-		add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
+        final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
+        final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
+        final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
+        final String queryParam = (params == null) ? null : params.getString("q", null);
+        final String searchParam = (params == null) ? "" : params.getString("s", null);
+        final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
+        final String repositoryId = (params == null) ? "" : params.getString(Lucene.rid.name(), null);
+        final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
 
-		// add search form
-		add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
 
-		// standard queries
-		add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.type.matches(TicketModel.Type.Proposal.name()),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        // add the user title panel
+        add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
 
-		add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.type.matches(TicketModel.Type.Bug.name()),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        // add search form
+        add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
 
-		add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.type.matches(TicketModel.Type.Enhancement.name()),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        // standard queries
+        add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.type.matches(TicketModel.Type.Proposal.name()),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-		add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.type.matches(TicketModel.Type.Task.name()),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        add(new BookmarkablePageLink<Void>("bugsQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.type.matches(TicketModel.Type.Bug.name()),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-		add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.type.matches(TicketModel.Type.Question.name()),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        add(new BookmarkablePageLink<Void>("enhancementsQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.type.matches(TicketModel.Type.Enhancement.name()),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-		add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class,
-				queryParameters(
-						null,
-						milestoneParam,
-						TicketsUI.openStatii,
-						null,
-						null,
-						true,
-						1)));
+        add(new BookmarkablePageLink<Void>("tasksQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.type.matches(TicketModel.Type.Task.name()),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-		add(new Label("userDivider"));
-		add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.createdby.matches(username),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        add(new BookmarkablePageLink<Void>("questionsQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.type.matches(TicketModel.Type.Question.name()),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
+        
+        add(new BookmarkablePageLink<Void>("maintenanceQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.type.matches(TicketModel.Type.Maintenance.name()),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-		add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.watchedby.matches(username),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
-		add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.mentions.matches(username),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
-		add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class,
-				queryParameters(
-						Lucene.responsible.matches(username),
-						milestoneParam,
-						statiiParam,
-						assignedToParam,
-						sortBy,
-						desc,
-						1)));
+        add(new BookmarkablePageLink<Void>("resetQuery", MyTicketsPage.class,
+                queryParameters(
+                        null,
+                        milestoneParam,
+                        TicketsUI.openStatii,
+                        null,
+                        null,
+                        true,
+                        null,
+                        1)));
 
-		// states
-		if (ArrayUtils.isEmpty(statiiParam)) {
-			add(new Label("selectedStatii", getString("gb.all")));
-		} else {
-			add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
-		}
-		add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
-		add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
-		add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
+        add(new Label("userDivider"));
+        add(new BookmarkablePageLink<Void>("createdQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.createdby.matches(username),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-		// by status
-		List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
-		statii.remove(Status.Closed);
-		ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
-		DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
-			private static final long serialVersionUID = 1L;
+        add(new BookmarkablePageLink<Void>("watchedQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.watchedby.matches(username),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
+        add(new BookmarkablePageLink<Void>("mentionsQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.mentions.matches(username),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
+        add(new BookmarkablePageLink<Void>("responsibleQuery", MyTicketsPage.class,
+                queryParameters(
+                        Lucene.responsible.matches(username),
+                        milestoneParam,
+                        statiiParam,
+                        assignedToParam,
+                        sortBy,
+                        desc,
+                        repositoryId,
+                        1)));
 
-			@Override
-			public void populateItem(final Item<Status> item) {
-				final Status status = item.getModelObject();
-				PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
-				String css = TicketsUI.getStatusClass(status);
-				item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
-			}
-		};
-		add(statiiLinks);
+        // states
+        if (ArrayUtils.isEmpty(statiiParam)) {
+            add(new Label("selectedStatii", getString("gb.all")));
+        } else {
+            add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
+        }
+        add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, repositoryId, 1)));
+        add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, repositoryId, 1)));
+        add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, repositoryId, 1)));
 
-		List<TicketSort> sortChoices = new ArrayList<TicketSort>();
-		sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
-		sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
-		sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
-		sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
-		sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
-		sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
-		sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
-		sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
-		sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
-		sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
+        // by status
+        List<Status> statii = new ArrayList<Status>(Arrays.asList(Status.values()));
+        statii.remove(Status.Closed);
+        ListDataProvider<Status> resolutionsDp = new ListDataProvider<Status>(statii);
+        DataView<Status> statiiLinks = new DataView<Status>("statii", resolutionsDp) {
+            private static final long serialVersionUID = 1L;
 
-		TicketSort currentSort = sortChoices.get(0);
-		for (TicketSort ts : sortChoices) {
-			if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
-				currentSort = ts;
-				break;
-			}
-		}
-		add(new Label("currentSort", currentSort.name));
+            @Override
+            public void populateItem(final Item<Status> item) {
+                final Status status = item.getModelObject();
+                PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, repositoryId, 1);
+                String css = TicketsUI.getStatusClass(status);
+                item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
+            }
+        };
+        add(statiiLinks);
 
-		ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
-		DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
-			private static final long serialVersionUID = 1L;
+        // by sort
+        List<TicketSort> sortChoices = new ArrayList<TicketSort>();
+        sortChoices.add(new TicketSort(getString("gb.sortNewest"), Lucene.created.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortOldest"), Lucene.created.name(), false));
+        sortChoices.add(new TicketSort(getString("gb.sortMostRecentlyUpdated"), Lucene.updated.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortLeastRecentlyUpdated"), Lucene.updated.name(), false));
+        sortChoices.add(new TicketSort(getString("gb.sortMostComments"), Lucene.comments.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortLeastComments"), Lucene.comments.name(), false));
+        sortChoices.add(new TicketSort(getString("gb.sortMostPatchsetRevisions"), Lucene.patchsets.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
+        sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
+        sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
+        sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
+        sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
 
-			@Override
-			public void populateItem(final Item<TicketSort> item) {
-				final TicketSort ts = item.getModelObject();
-				PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, 1);
-				item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
-			}
-		};
-		add(sortMenu);
+        TicketSort currentSort = sortChoices.get(0);
+        for (TicketSort ts : sortChoices) {
+            if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
+                currentSort = ts;
+                break;
+            }
+        }
+        add(new Label("currentSort", currentSort.name));
 
-		// Build Query here
-		QueryBuilder qb = new QueryBuilder(queryParam);
-		if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
-			// specify the states
-			boolean not = false;
-			QueryBuilder q = new QueryBuilder();
-			for (String state : statiiParam) {
-				if (state.charAt(0) == '!') {
-					not = true;
-					q.and(Lucene.status.doesNotMatch(state.substring(1)));
-				} else {
-					q.or(Lucene.status.matches(state));
-				}
-			}
-			if (not) {
-				qb.and(q.toString());
-			} else {
-				qb.and(q.toSubquery().toString());
-			}
-		}
+        ListDataProvider<TicketSort> sortChoicesDp = new ListDataProvider<TicketSort>(sortChoices);
+        DataView<TicketSort> sortMenu = new DataView<TicketSort>("sort", sortChoicesDp) {
+            private static final long serialVersionUID = 1L;
 
-		final String luceneQuery;
-		if (qb.containsField(Lucene.createdby.name())
-				|| qb.containsField(Lucene.responsible.name())
-				|| qb.containsField(Lucene.watchedby.name())) {
-			// focused "my tickets" query
-			luceneQuery = qb.build();
-		} else {
-			// general "my tickets" query
-			QueryBuilder myQuery = new QueryBuilder();
-			myQuery.or(Lucene.createdby.matches(username));
-			myQuery.or(Lucene.responsible.matches(username));
-			myQuery.or(Lucene.watchedby.matches(username));
-			myQuery.and(qb.toSubquery().toString());
-			luceneQuery = myQuery.build();
-		}
+            @Override
+            public void populateItem(final Item<TicketSort> item) {
+                final TicketSort ts = item.getModelObject();
+                PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, ts.sortBy, ts.desc, repositoryId, 1);
+                item.add(new LinkPanel("sortLink", null, ts.name, MyTicketsPage.class, params).setRenderBodyOnly(true));
+            }
+        };
+        add(sortMenu);
 
-		// paging links
-		int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
-		int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
+        // by repository
+        final List<QueryResult> tickets =
+            query(initializeQueryBuilder(null, username), 1, Integer.MAX_VALUE, sortBy, desc);
+        final List<RepositoryModel> repositoryChoices = correspondingRepositories(tickets);
+        Collections.sort(repositoryChoices);
 
-		List<QueryResult> results;
-		if(StringUtils.isEmpty(searchParam)) {
-			results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
-		} else {
-			results = app().tickets().searchFor(null, searchParam, page, pageSize);
-		}
+        final RepositoryModel noneChoice = new RepositoryModel();
+        noneChoice.name = getString("gb.all");
+        repositoryChoices.add(0, noneChoice);
 
-		int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
-		buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
+        RepositoryModel currentRepository = repositoryChoices.get(0);
+        for (RepositoryModel r : repositoryChoices) {
+            if (r.getRID().equals(repositoryId)) {
+                currentRepository = r;
+                break;
+            }
+        }
+        add(new Label("currentRepository", currentRepository.toString()));
 
-		final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
-		add(new TicketListPanel("ticketList", results, showSwatch, true));
-	}
+        ListDataProvider<RepositoryModel> repositoryChoicesDp = new ListDataProvider<RepositoryModel>(repositoryChoices);
+        DataView<RepositoryModel> repositoryMenu = new DataView<RepositoryModel>("repository", repositoryChoicesDp) {
+            private static final long serialVersionUID = 1L;
 
-	protected PageParameters queryParameters(
-			String query,
-			String milestone,
-			String[] states,
-			String assignedTo,
-			String sort,
-			boolean descending,
-			int page) {
+            @Override
+            public void populateItem(final Item<RepositoryModel> item) {
+                final RepositoryModel r = item.getModelObject();
+                String rid = r == noneChoice ? null : r.getRID();
+                PageParameters params = queryParameters(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, rid, 1);
+                item.add(new LinkPanel("repositoryLink", null, r.toString(), MyTicketsPage.class, params).setRenderBodyOnly(true));
+            }
+        };
+        add(repositoryMenu);
 
-		PageParameters params = WicketUtils.newRepositoryParameter("");
-		if (!StringUtils.isEmpty(query)) {
-			params.add("q", query);
-		}
-		if (!StringUtils.isEmpty(milestone)) {
-			params.add(Lucene.milestone.name(), milestone);
-		}
-		if (!ArrayUtils.isEmpty(states)) {
-			for (String state : states) {
-				params.add(Lucene.status.name(), state);
-			}
-		}
-		if (!StringUtils.isEmpty(assignedTo)) {
-			params.add(Lucene.responsible.name(), assignedTo);
-		}
-		if (!StringUtils.isEmpty(sort)) {
-			params.add("sort", sort);
-		}
-		if (!descending) {
-			params.add("direction", "asc");
-		}
-		if (page > 1) {
-			params.add("pg", "" + page);
-		}
-		return params;
-	}
+        // Update query with filter criteria
+        final QueryBuilder qb = initializeQueryBuilder(queryParam, username);
 
-	protected void buildPager(
-			final String query,
-			final String milestone,
-			final String [] states,
-			final String assignedTo,
-			final String sort,
-			final boolean desc,
-			final int page,
-			int pageSize,
-			int count,
-			int total) {
+        if (!qb.containsField(Lucene.status.name()) && !ArrayUtils.isEmpty(statiiParam)) {
+            // specify the states
+            boolean not = false;
+            QueryBuilder q = new QueryBuilder();
+            for (String state : statiiParam) {
+                if (state.charAt(0) == '!') {
+                    not = true;
+                    q.and(Lucene.status.doesNotMatch(state.substring(1)));
+                } else {
+                    q.or(Lucene.status.matches(state));
+                }
+            }
 
-		boolean showNav = total > (2 * pageSize);
-		boolean allowPrev = page > 1;
-		boolean allowNext = (pageSize * (page - 1) + count) < total;
-		add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page - 1)).setEnabled(allowPrev).setVisible(showNav));
-		add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, page + 1)).setEnabled(allowNext).setVisible(showNav));
+            if (not) {
+                qb.and(q.toString());
+            } else {
+                qb.and(q.toSubquery().toString());
+            }
+        }
 
-		if (total <= pageSize) {
-			add(new Label("pageLink").setVisible(false));
-			return;
-		}
+        if (noneChoice != currentRepository && !qb.containsField(Lucene.rid.name())) {
+            QueryBuilder q1 = new QueryBuilder();
+            q1.and(Lucene.rid.matches(repositoryId));
+            qb.and(q1.toSubquery().toString());
+        }
 
-		// determine page numbers to display
-		int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
-		// preferred number of pagelinks
-		int segments = 5;
-		if (pages < segments) {
-			// not enough data for preferred number of page links
-			segments = pages;
-		}
-		int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
-		int maxpage = Math.min(pages, minpage + (segments - 1));
-		List<Integer> sequence = new ArrayList<Integer>();
-		for (int i = minpage; i <= maxpage; i++) {
-			sequence.add(i);
-		}
+        // paging links
+        int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
+        int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
 
-		ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
-		DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
-			private static final long serialVersionUID = 1L;
+        final List<QueryResult> results = 
+            StringUtils.isEmpty(searchParam) ? query(qb, page, pageSize, sortBy, desc) : search(searchParam, page, pageSize);
 
-			@Override
-			public void populateItem(final Item<Integer> item) {
-				final Integer i = item.getModelObject();
-				LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, i));
-				link.setRenderBodyOnly(true);
-				if (i == page) {
-					WicketUtils.setCssClass(item, "active");
-				}
-				item.add(link);
-			}
-		};
-		add(pagesView);
-	}
+        int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
+        buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, repositoryId, page, pageSize, results.size(), totalResults);
+
+        final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
+        add(new TicketListPanel("ticketList", results, showSwatch, true));
+    }
+
+    protected PageParameters queryParameters(
+            String query,
+            String milestone,
+            String[] states,
+            String assignedTo,
+            String sort,
+            boolean descending,
+            String repositoryId,
+            int page) {
+
+        PageParameters params = WicketUtils.newRepositoryParameter("");
+        if (!StringUtils.isEmpty(query)) {
+            params.add("q", query);
+        }
+        if (!StringUtils.isEmpty(milestone)) {
+            params.add(Lucene.milestone.name(), milestone);
+        }
+        if (!ArrayUtils.isEmpty(states)) {
+            for (String state : states) {
+                params.add(Lucene.status.name(), state);
+            }
+        }
+        if (!StringUtils.isEmpty(assignedTo)) {
+            params.add(Lucene.responsible.name(), assignedTo);
+        }
+        if (!StringUtils.isEmpty(sort)) {
+            params.add("sort", sort);
+        }
+        if (!descending) {
+            params.add("direction", "asc");
+        }
+        if (!StringUtils.isEmpty(repositoryId)) {
+            params.add(Lucene.rid.name(), repositoryId);
+        }
+        if (page > 1) {
+            params.add("pg", "" + page);
+        }
+        return params;
+    }
+
+    protected void buildPager(
+            final String query,
+            final String milestone,
+            final String [] states,
+            final String assignedTo,
+            final String sort,
+            final boolean desc,
+            final String repositoryId,
+            final int page,
+            int pageSize,
+            int count,
+            int total) {
+
+        boolean showNav = total > (2 * pageSize);
+        boolean allowPrev = page > 1;
+        boolean allowNext = (pageSize * (page - 1) + count) < total;
+        add(new BookmarkablePageLink<Void>("prevLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page - 1)).setEnabled(allowPrev).setVisible(showNav));
+        add(new BookmarkablePageLink<Void>("nextLink", MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, page + 1)).setEnabled(allowNext).setVisible(showNav));
+
+        if (total <= pageSize) {
+            add(new Label("pageLink").setVisible(false));
+            return;
+        }
+
+        // determine page numbers to display
+        int pages = count == 0 ? 0 : ((total / pageSize) + (total % pageSize == 0 ? 0 : 1));
+        // preferred number of pagelinks
+        int segments = 5;
+        if (pages < segments) {
+            // not enough data for preferred number of page links
+            segments = pages;
+        }
+        int minpage = Math.min(Math.max(1, page - 2), pages - (segments - 1));
+        int maxpage = Math.min(pages, minpage + (segments - 1));
+        List<Integer> sequence = new ArrayList<Integer>();
+        for (int i = minpage; i <= maxpage; i++) {
+            sequence.add(i);
+        }
+
+        ListDataProvider<Integer> pagesDp = new ListDataProvider<Integer>(sequence);
+        DataView<Integer> pagesView = new DataView<Integer>("pageLink", pagesDp) {
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void populateItem(final Item<Integer> item) {
+                final Integer i = item.getModelObject();
+                LinkPanel link = new LinkPanel("page", null, "" + i, MyTicketsPage.class, queryParameters(query, milestone, states, assignedTo, sort, desc, repositoryId, i));
+                link.setRenderBodyOnly(true);
+                if (i == page) {
+                    WicketUtils.setCssClass(item, "active");
+                }
+                item.add(link);
+            }
+        };
+        add(pagesView);
+    }
+
+    private QueryBuilder initializeQueryBuilder(String queryparam, String username) {
+        final QueryBuilder qb = new QueryBuilder(queryparam);
+
+        // focused "my tickets"
+        if (qb.containsField(Lucene.createdby.name())
+                || qb.containsField(Lucene.responsible.name())
+                || qb.containsField(Lucene.watchedby.name())
+                || qb.containsField(Lucene.mentions.name())) {
+
+            return qb;
+        }
+
+        // general "my tickets"
+        return qb.andSubquery()
+                 .or(Lucene.createdby.matches(username))
+                 .or(Lucene.responsible.matches(username))
+                 .or(Lucene.watchedby.matches(username))
+                 .or(Lucene.mentions.matches(username))
+                 .endSubquery();
+    }
+
+    private List<QueryResult> query(QueryBuilder qb, int page, int pageSize, String sortBy, boolean descending) {
+        return app().tickets().queryFor(qb.build(), page, pageSize, sortBy, descending);
+    }
+
+    private List<QueryResult> search(String searchParam, int page, int pageSize) {
+        return app().tickets().searchFor(null, searchParam, page, pageSize);
+    }
+
+    private List<RepositoryModel> correspondingRepositories(Collection<QueryResult> tickets) { 
+        final HashMap<String, RepositoryModel> result = new HashMap<>();
+        for (QueryResult ticket : tickets) {
+            RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
+            if (!result.containsKey(repository.getRID())) {
+                result.put(repository.getRID(), repository);
+            }
+        }
+
+        return new ArrayList<>(result.values());
+    }
 }
diff --git a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
index b6c2359..d2589e6 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/NewRepositoryPage.java
@@ -359,14 +359,14 @@
 					}
 				}
 			} finally {
-				revWalk.release();
+				revWalk.close();
 			}
 		} catch (UnsupportedEncodingException e) {
 			logger().error(null, e);
 		} catch (IOException e) {
 			logger().error(null, e);
 		} finally {
-			odi.release();
+			odi.close();
 			db.close();
 		}
 		return success;
diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
index 447c6aa..9b5af02 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.html
@@ -39,6 +39,8 @@
 				</div>
 			</td></tr>
 			<tr><th><wicket:message key="gb.type"></wicket:message><span style="color:red;">*</span></th><td class="edit"><select class="input-large" wicket:id="type"></select></td></tr>
+            <tr><th><wicket:message key="gb.severity"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="severity"></select></td></tr>
+            <tr wicket:id="priority"></tr>
 			<tr wicket:id="responsible"></tr>
 			<tr wicket:id="milestone"></tr>
 			<tr wicket:id="mergeto"></tr>
@@ -67,5 +69,9 @@
 	<th><wicket:message key="gb.mergeTo"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="mergeto"></select></td>
 </wicket:fragment>
 
+<wicket:fragment wicket:id="priorityFragment">
+    <th><wicket:message key="gb.priority"></wicket:message></th><td class="edit"><select class="input-large" wicket:id="priority"></select></td>
+</wicket:fragment>
+
 </wicket:extend>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
index 8f28055..0c52505 100644
--- a/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/NewTicketPage.java
@@ -76,6 +76,10 @@
 
 	private Label descriptionPreview;
 
+	private IModel<TicketModel.Priority> priorityModel;
+
+	private IModel<TicketModel.Severity> severityModel;
+
 	public NewTicketPage(PageParameters params) {
 		super(params);
 
@@ -95,6 +99,8 @@
 		mergeToModel = Model.of(Repository.shortenRefName(getRepositoryModel().mergeTo));
 		responsibleModel = Model.of();
 		milestoneModel = Model.of();
+		severityModel = Model.of(TicketModel.Severity.defaultSeverity);
+		priorityModel = Model.of(TicketModel.Priority.defaultPriority);
 
 		setStatelessHint(false);
 		setOutputMarkupId(true);
@@ -105,6 +111,7 @@
 		form.add(new DropDownChoice<TicketModel.Type>("type", typeModel, Arrays.asList(TicketModel.Type.choices())));
 		form.add(new TextField<String>("title", titleModel));
 		form.add(new TextField<String>("topic", topicModel));
+		form.add(new DropDownChoice<TicketModel.Severity>("severity", severityModel, Arrays.asList(TicketModel.Severity.choices())));
 
 		final IModel<String> markdownPreviewModel = Model.of();
 		descriptionPreview = new Label("descriptionPreview", markdownPreviewModel);
@@ -152,6 +159,11 @@
 			milestone.add(new DropDownChoice<TicketMilestone>("milestone", milestoneModel, milestones));
 			form.add(milestone.setVisible(!milestones.isEmpty()));
 
+			// priority
+			Fragment priority = new Fragment("priority", "priorityFragment", this);
+			priority.add(new DropDownChoice<TicketModel.Priority>("priority", priorityModel, Arrays.asList(TicketModel.Priority.choices())));
+			form.add(priority);
+
 			// integration branch
 			List<String> branches = new ArrayList<String>();
 			for (String branch : getRepositoryModel().getLocalBranches()) {
@@ -171,6 +183,7 @@
 			form.add(new Label("responsible").setVisible(false));
 			form.add(new Label("milestone").setVisible(false));
 			form.add(new Label("mergeto").setVisible(false));
+			form.add(new Label("priority").setVisible(false));
 		}
 
 		form.add(new AjaxButton("create") {
@@ -212,6 +225,20 @@
 					change.setField(Field.milestone, milestone.name);
 				}
 
+				// severity
+				TicketModel.Severity severity = TicketModel.Severity.defaultSeverity;
+				if (severityModel.getObject() != null) {
+					severity = severityModel.getObject();
+				}
+				change.setField(Field.severity, severity);
+
+				// priority
+				TicketModel.Priority priority = TicketModel.Priority.defaultPriority;
+				if (priorityModel.getObject() != null) {
+					priority = priorityModel.getObject();
+				}
+				change.setField(Field.priority, priority);
+
 				// integration branch
 				String mergeTo = mergeToModel.getObject();
 				if (!StringUtils.isEmpty(mergeTo)) {
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
index 2d446ec..fc28d17 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.html
@@ -8,7 +8,7 @@
 <wicket:extend>
 <div class="container">
 	
-	<table class="repositories">
+	<table class="repositories projectlist">
 		<thead>
 			<tr>	
 				<th class="left">
diff --git a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
index f04fa78..132a39d 100644
--- a/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ProjectsPage.java
@@ -15,6 +15,7 @@
  */
 package com.gitblit.wicket.pages;
 
+import java.util.Collections;
 import java.util.List;
 
 import org.apache.wicket.PageParameters;
@@ -69,6 +70,7 @@
 		}
 
 		List<ProjectModel> projects = getProjects(params);
+		Collections.sort(projects);
 
 		ListDataProvider<ProjectModel> dp = new ListDataProvider<ProjectModel>(projects);
 
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
index c0ac7eb..bd59494 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.html
@@ -9,7 +9,7 @@
 
 	<!-- pager links -->
 	<div class="page_nav2">
-		<a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+		<a wicket:id="firstPageTop"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageTop">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageTop"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
 	</div>
 	
 	<!-- ref log -->
@@ -17,7 +17,7 @@
 
 	<!-- pager links -->
 	<div style="padding-bottom:5px;">
-		<a wicket:id="firstPage"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPage">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPage"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
+		<a wicket:id="firstPageBottom"><wicket:message key="gb.pageFirst"></wicket:message></a> | <a wicket:id="prevPageBottom">&laquo; <wicket:message key="gb.pagePrevious"></wicket:message></a> | <a wicket:id="nextPageBottom"><wicket:message key="gb.pageNext"></wicket:message> &raquo;</a> 
 	</div>
 	
 </wicket:extend>
diff --git a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
index 29fd449..44fb222 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ReflogPage.java
@@ -40,15 +40,26 @@
 		boolean hasMore = reflogPanel.hasMore();
 		add(reflogPanel);
 
-		add(new BookmarkablePageLink<Void>("firstPage", ReflogPage.class,
+		add(new BookmarkablePageLink<Void>("firstPageTop", ReflogPage.class,
 				WicketUtils.newObjectParameter(repositoryName, objectId))
 				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("prevPage", ReflogPage.class,
+		add(new BookmarkablePageLink<Void>("prevPageTop", ReflogPage.class,
 				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
 				.setEnabled(pageNumber > 1));
-		add(new BookmarkablePageLink<Void>("nextPage", ReflogPage.class,
+		add(new BookmarkablePageLink<Void>("nextPageTop", ReflogPage.class,
 				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
 				.setEnabled(hasMore));
+
+		add(new BookmarkablePageLink<Void>("firstPageBottom", ReflogPage.class,
+				WicketUtils.newObjectParameter(repositoryName, objectId))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("prevPageBottom", ReflogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, prevPage))
+				.setEnabled(pageNumber > 1));
+		add(new BookmarkablePageLink<Void>("nextPageBottom", ReflogPage.class,
+				WicketUtils.newLogPageParameter(repositoryName, objectId, nextPage))
+				.setEnabled(hasMore));
+
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
index 22544bc..d413242 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
@@ -24,13 +24,20 @@
 							</div>
 							
 							<div class="span7">
-								<div>
-									<span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
-									<a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
-										<img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
-									</a>
+                                <div style="display:inline-block;vertical-align:top;padding-right:5px;">
+                                    <span wicket:id="repoIcon"></span>
+                                </div>
+								<div style="display:inline-block;">
+                                    <div>
+									   <span class="project" wicket:id="projectTitle">[project title]</span>/<span class="repository" wicket:id="repositoryName">[repository name]</span>
+									   <a class="hidden-phone hidden-tablet" style="text-decoration: none;" wicket:id="syndication" wicket:message="title:gb.feed">
+										  <img style="border:0px;vertical-align:baseline;" src="feed_16x16.png"></img>
+									   </a>
+                                    </div>
+                                    <div>
+                                        <span class="gray" wicket:id="originRepository">[origin repository]</span>   
+                                    </div>
 								</div>
-								<span wicket:id="originRepository">[origin repository]</span>								
 							</div>
 						</div>
 					</div>
@@ -60,6 +67,22 @@
 			<i wicket:id="icon" style="padding-right:3px;"></i><span wicket:id="label"></span>
 		</wicket:fragment>
 		
+        <wicket:fragment wicket:id="repoIconFragment">
+            <span class="gray mega-octicon octicon-repo"></span>
+        </wicket:fragment>
+
+        <wicket:fragment wicket:id="mirrorIconFragment">
+            <span class="gray mega-octicon octicon-mirror"></span>
+        </wicket:fragment>
+
+        <wicket:fragment wicket:id="forkIconFragment">
+            <span class="gray mega-octicon octicon-repo-forked"></span>
+        </wicket:fragment>
+
+        <wicket:fragment wicket:id="cloneIconFragment">
+            <span class="gray mega-octicon octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+        </wicket:fragment>
+    
 		<wicket:fragment wicket:id="originFragment">
 			<p class="originRepository"><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
 		</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index 134ee04..7e164a8 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -64,6 +64,7 @@
 import com.gitblit.utils.BugtraqProcessor;
 import com.gitblit.utils.DeepCopier;
 import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.ModelUtils;
 import com.gitblit.utils.RefLogUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.CacheControl;
@@ -73,6 +74,7 @@
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.NavigationPanel;
 import com.gitblit.wicket.panels.RefsPanel;
+import com.google.common.base.Optional;
 
 public abstract class RepositoryPage extends RootPage {
 
@@ -295,25 +297,38 @@
 		RepositoryModel model = getRepositoryModel();
 		if (StringUtils.isEmpty(model.originRepository)) {
 			if (model.isMirror) {
+				add(new Fragment("repoIcon", "mirrorIconFragment", this));
 				Fragment mirrorFrag = new Fragment("originRepository", "mirrorFragment", this);
 				Label lbl = new Label("originRepository", MessageFormat.format(getString("gb.mirrorOf"), "<b>" + model.origin + "</b>"));
 				mirrorFrag.add(lbl.setEscapeModelStrings(false));
 				add(mirrorFrag);
 			} else {
-				add(new Label("originRepository").setVisible(false));
+				if (model.isBare) {
+					add(new Fragment("repoIcon", "repoIconFragment", this));
+				} else {
+					add(new Fragment("repoIcon", "cloneIconFragment", this));
+				}
+				add(new Label("originRepository", Optional.of(model.description).or("")));
 			}
 		} else {
 			RepositoryModel origin = app().repositories().getRepositoryModel(model.originRepository);
 			if (origin == null) {
-				// no origin repository
-				add(new Label("originRepository").setVisible(false));
+				// no origin repository, show description if available
+				if (model.isBare) {
+					add(new Fragment("repoIcon", "repoIconFragment", this));
+				} else {
+					add(new Fragment("repoIcon", "cloneIconFragment", this));
+				}
+				add(new Label("originRepository", Optional.of(model.description).or("")));
 			} else if (!user.canView(origin)) {
 				// show origin repository without link
+				add(new Fragment("repoIcon", "forkIconFragment", this));
 				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
 				forkFrag.add(new Label("originRepository", StringUtils.stripDotGit(model.originRepository)));
 				add(forkFrag);
 			} else {
 				// link to origin repository
+				add(new Fragment("repoIcon", "forkIconFragment", this));
 				Fragment forkFrag = new Fragment("originRepository", "originFragment", this);
 				forkFrag.add(new LinkPanel("originRepository", null, StringUtils.stripDotGit(model.originRepository),
 						SummaryPage.class, WicketUtils.newRepositoryParameter(model.originRepository)));
@@ -356,8 +371,10 @@
 			add(new ExternalLink("myForkLink", "").setVisible(false));
 		} else {
 			String fork = app().repositories().getFork(user.username, model.name);
+			String userRepo = ModelUtils.getPersonalPath(user.username) + "/" + StringUtils.stripDotGit(StringUtils.getLastPathElement(model.name));
+			boolean hasUserRepo = app().repositories().hasRepository(userRepo);
 			boolean hasFork = fork != null;
-			boolean canFork = user.canFork(model) && model.hasCommits;
+			boolean canFork = user.canFork(model) && model.hasCommits && !hasUserRepo;
 
 			if (hasFork || !canFork) {
 				// user not allowed to fork or fork already exists or repo forbids forking
diff --git a/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
index 8aec9e6..ceca1ec 100644
--- a/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java
@@ -76,8 +76,9 @@
 		sb.append(asParam(p, proposal.name, "exclude", ""));
 		sb.append(asParam(p, proposal.name, "include", ""));
 
+		final int tabLength = app().settings().getInteger(Keys.web.tabLength, 4);
 		add(new Label("definition", StringUtils.breakLinesForHtml(StringUtils.escapeForHtml(sb
-				.toString().trim(), true))).setEscapeModelStrings(false));
+				.toString().trim(), true, tabLength))).setEscapeModelStrings(false));
 
 		List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(
 				proposal.repositories.values());
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.html b/src/main/java/com/gitblit/wicket/pages/RootPage.html
index 2ff305f..c51db81 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.html
@@ -14,7 +14,7 @@
             			<span class="icon-bar"></span>
           		</a>
 				<a class="brand" wicket:id="rootLink">
-					<img src="logo.png" height="45" width="120" class="logo"/>
+					<img src="logo.png" width="120" class="logo"/>
 				</a>
 				
 				<div class="nav-collapse">
diff --git a/src/main/java/com/gitblit/wicket/pages/RootPage.java b/src/main/java/com/gitblit/wicket/pages/RootPage.java
index c4d4dd1..6ed5a35 100644
--- a/src/main/java/com/gitblit/wicket/pages/RootPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RootPage.java
@@ -36,11 +36,11 @@
 
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.PageParameters;
+import org.apache.wicket.RequestCycle;
 import org.apache.wicket.behavior.HeaderContributor;
 import org.apache.wicket.markup.html.IHeaderContributor;
 import org.apache.wicket.markup.html.IHeaderResponse;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.PasswordTextField;
 import org.apache.wicket.markup.html.form.TextField;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.markup.html.panel.Fragment;
@@ -71,9 +71,10 @@
 import com.gitblit.utils.ModelUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.NonTrimmedPasswordTextField;
 import com.gitblit.wicket.SessionlessForm;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.NavigationPanel;
 
@@ -151,6 +152,7 @@
 		boolean authenticateAdmin = app().settings().getBoolean(Keys.web.authenticateAdminPages, true);
 		boolean allowAdmin = app().settings().getBoolean(Keys.web.allowAdministration, true);
 		boolean allowLucene = app().settings().getBoolean(Keys.web.allowLuceneIndexing, true);
+		boolean displayUserPanel = app().settings().getBoolean(Keys.web.displayUserPanel, true);
 		boolean isLoggedIn = GitBlitWebSession.get().isLoggedIn();
 
 		if (authenticateAdmin) {
@@ -168,7 +170,7 @@
 			}
 		}
 
-		if (authenticateView || authenticateAdmin) {
+		if (displayUserPanel && (authenticateView || authenticateAdmin)) {
 			if (isLoggedIn) {
 				UserMenu userFragment = new UserMenu("userPanel", "userMenuFragment", RootPage.this);
 				add(userFragment);
@@ -183,6 +185,11 @@
 		// navigation links
 		List<NavLink> navLinks = new ArrayList<NavLink>();
 		if (!authenticateView || (authenticateView && isLoggedIn)) {
+			UserModel user = UserModel.ANONYMOUS;
+			if (isLoggedIn) {
+				user = GitBlitWebSession.get().getUser();
+			}
+
 			navLinks.add(new PageNavLink(isLoggedIn ? "gb.myDashboard" : "gb.dashboard", MyDashboardPage.class,
 					getRootPageParameters()));
 			if (isLoggedIn && app().tickets().isReady()) {
@@ -190,6 +197,9 @@
 			}
 			navLinks.add(new PageNavLink("gb.repositories", RepositoriesPage.class,
 					getRootPageParameters()));
+			if (user.canAdmin()) {
+				navLinks.add(new PageNavLink("gb.filestore", FilestorePage.class, getRootPageParameters()));
+			}
 			navLinks.add(new PageNavLink("gb.activity", ActivityPage.class, getRootPageParameters()));
 			if (allowLucene) {
 				navLinks.add(new PageNavLink("gb.search", LuceneSearchPage.class));
@@ -197,11 +207,6 @@
 
 			if (!authenticateView || (authenticateView && isLoggedIn)) {
 				addDropDownMenus(navLinks);
-			}
-
-			UserModel user = UserModel.ANONYMOUS;
-			if (isLoggedIn) {
-				user = GitBlitWebSession.get().getUser();
 			}
 
 			// add nav link extensions
@@ -278,7 +283,7 @@
 
 			request = ((WebRequest) getRequest()).getHttpServletRequest();
 			response = ((WebResponse) getResponse()).getHttpServletResponse();
-			request.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, AuthenticationType.CREDENTIALS);
+			request.getSession().setAttribute(Constants.ATTRIB_AUTHTYPE, AuthenticationType.CREDENTIALS);
 
 			// Set Cookie
 			app().authentication().setCookie(request, response, user);
@@ -564,7 +569,9 @@
 					String username = RootPage.this.username.getObject();
 					char[] password = RootPage.this.password.getObject().toCharArray();
 
-					UserModel user = app().authentication().authenticate(username, password);
+					HttpServletRequest request = ((WebRequest)RequestCycle.get().getRequest()).getHttpServletRequest();
+
+					UserModel user = app().authentication().authenticate(username, password, request.getRemoteAddr());
 					if (user == null) {
 						error(getString("gb.invalidUsernameOrPassword"));
 					} else if (user.username.equals(Constants.FEDERATION_USER)) {
@@ -580,7 +587,7 @@
 			TextField<String> unameField = new TextField<String>("username", username);
 			WicketUtils.setInputPlaceholder(unameField, markupProvider.getString("gb.username"));
 			loginForm.add(unameField);
-			PasswordTextField pwField = new PasswordTextField("password", password);
+			NonTrimmedPasswordTextField pwField = new NonTrimmedPasswordTextField("password", password);
 			WicketUtils.setInputPlaceholder(pwField, markupProvider.getString("gb.password"));
 			loginForm.add(pwField);
 			add(loginForm);
@@ -607,11 +614,11 @@
 			UserModel user = session.getUser();
 			boolean editCredentials = app().authentication().supportsCredentialChanges(user);
 			HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest();
-			AuthenticationType authenticationType = (AuthenticationType) request.getSession().getAttribute(Constants.AUTHENTICATION_TYPE);
-			boolean standardLogin = authenticationType.isStandard();
+			AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
+			boolean standardLogin = (null != authenticationType) ? authenticationType.isStandard() : true;
 
 			if (app().settings().getBoolean(Keys.web.allowGravatar, true)) {
-				add(new GravatarImage("username", user, "navbarGravatar", 20, false));
+				add(new AvatarImage("username", user, "navbarGravatar", 20, false));
 			} else {
 				add(new Label("username", user.getDisplayName()));
 			}
diff --git a/src/main/java/com/gitblit/wicket/pages/SessionPage.java b/src/main/java/com/gitblit/wicket/pages/SessionPage.java
index 0dda949..bcf8e97 100644
--- a/src/main/java/com/gitblit/wicket/pages/SessionPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SessionPage.java
@@ -56,34 +56,50 @@
 		HttpServletRequest request = ((WebRequest) getRequest()).getHttpServletRequest();
 		HttpServletResponse response = ((WebResponse) getResponse()).getHttpServletResponse();
 
-		if (session.isLoggedIn() && !session.isSessionInvalidated()) {
-			// already have a session, refresh usermodel to pick up
-			// any changes to permissions or roles (issue-186)
-			UserModel user = app().users().getUserModel(session.getUser().username);
+		// If using container/external servlet authentication, use request attribute
+		String authedUser = (String) request.getAttribute(Constants.ATTRIB_AUTHUSER);
 
-			if (user == null || user.disabled) {
-				// user was deleted/disabled during session
-				app().authentication().logout(request, response, user);
-				session.setUser(null);
-				session.invalidateNow();
-				return;
+		// Default to trusting session authentication if not set in request by external processing
+		if (StringUtils.isEmpty(authedUser) && session.isLoggedIn()) {
+			authedUser = session.getUsername();
+		}
+
+		if (!StringUtils.isEmpty(authedUser)) {
+			// Avoid session fixation for non-session authentication
+			// If the authenticated user is different from the session user, discard
+			// the old session entirely, without trusting any session values
+			if (!authedUser.equals(session.getUsername())) {
+				session.replaceSession();
 			}
 
-			// validate cookie during session (issue-361)
-			if (user != null && app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) {
-				String requestCookie = app().authentication().getCookie(request);
-				if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) {
-					if (!requestCookie.equals(user.cookie)) {
-						// cookie was changed during our session
-						app().authentication().logout(request, response, user);
-						session.setUser(null);
-						session.invalidateNow();
-						return;
+			if (!session.isSessionInvalidated()) {
+				// Refresh usermodel to pick up any changes to permissions or roles (issue-186)
+				UserModel user = app().users().getUserModel(authedUser);
+
+				if (user == null || user.disabled) {
+					// user was deleted/disabled during session
+					app().authentication().logout(request, response, user);
+					session.setUser(null);
+					session.invalidateNow();
+					return;
+				}
+
+				// validate cookie during session (issue-361)
+				if (app().settings().getBoolean(Keys.web.allowCookieAuthentication, true)) {
+					String requestCookie = app().authentication().getCookie(request);
+					if (!StringUtils.isEmpty(requestCookie) && !StringUtils.isEmpty(user.cookie)) {
+						if (!requestCookie.equals(user.cookie)) {
+							// cookie was changed during our session
+							app().authentication().logout(request, response, user);
+							session.setUser(null);
+							session.invalidateNow();
+							return;
+						}
 					}
 				}
+				session.setUser(user);
+				return;
 			}
-			session.setUser(user);
-			return;
 		}
 
 		// try to authenticate by servlet request
@@ -91,15 +107,16 @@
 
 		// Login the user
 		if (user != null) {
-			// preserve the authentication type across session replacement
-			AuthenticationType authenticationType = (AuthenticationType) request.getSession()
-					.getAttribute(Constants.AUTHENTICATION_TYPE);
+			AuthenticationType authenticationType = (AuthenticationType) request.getAttribute(Constants.ATTRIB_AUTHTYPE);
 
 			// issue 62: fix session fixation vulnerability
-			session.replaceSession();
+			// but only if authentication was done in the container.
+			// It avoid double change of session, that some authentication method
+			// don't like
+			if (AuthenticationType.CONTAINER != authenticationType) {
+				session.replaceSession();
+			}
 			session.setUser(user);
-
-			request.getSession().setAttribute(Constants.AUTHENTICATION_TYPE, authenticationType);
 
 			// Set Cookie
 			app().authentication().setCookie(request, response, user);
diff --git a/src/main/java/com/gitblit/wicket/pages/TagPage.java b/src/main/java/com/gitblit/wicket/pages/TagPage.java
index 9eed279..ffeea6f 100644
--- a/src/main/java/com/gitblit/wicket/pages/TagPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TagPage.java
@@ -31,7 +31,7 @@
 import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
+import com.gitblit.wicket.panels.AvatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.RefsPanel;
 
@@ -77,7 +77,7 @@
 			linkClass = CommitPage.class;
 			break;
 		}
-		add(new GravatarImage("taggerAvatar", tagRef.getAuthorIdent()));
+		add(new AvatarImage("taggerAvatar", tagRef.getAuthorIdent()));
 
 		add(new RefsPanel("tagName", repositoryName, Arrays.asList(tagRef)));
 		add(new Label("tagId", tagRef.getObjectId().getName()));
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.html b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
index f3f38ec..5ae005e 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.html
@@ -67,6 +67,8 @@
 				<div style="border: 1px solid #ccc;padding: 10px;margin: 5px 0px;">
 					<table class="summary" style="width: 100%">
 						<tr><th><wicket:message key="gb.type"></wicket:message></th><td><span wicket:id="ticketType">[type]</span></td></tr>
+						<tr><th><wicket:message key="gb.priority"></wicket:message></th><td><span wicket:id="priority">[priority]</span></td></tr>
+						<tr><th><wicket:message key="gb.severity"></wicket:message></th><td><span wicket:id="severity">[severity]</span></td></tr>
 						<tr><th><wicket:message key="gb.topic"></wicket:message></th><td><span wicket:id="ticketTopic">[topic]</span></td></tr>
 						<tr><th><wicket:message key="gb.responsible"></wicket:message></th><td><span wicket:id="responsible">[responsible]</span></td></tr>
 						<tr><th><wicket:message key="gb.milestone"></wicket:message></th><td><span wicket:id="milestone">[milestone]</span></td></tr>
@@ -554,7 +556,6 @@
 		</div>
 	</div>
 </wicket:fragment>
-
 
 <!-- VETOED PATCHSET FRAGMENT -->
 <wicket:fragment wicket:id="vetoedFragment">
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
index 19788f2..8bf5c6d 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -88,10 +88,10 @@
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.TicketsUI;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.AvatarImage;
 import com.gitblit.wicket.panels.BasePanel.JavascriptTextPrompt;
 import com.gitblit.wicket.panels.CommentPanel;
 import com.gitblit.wicket.panels.DiffStatPanel;
-import com.gitblit.wicket.panels.GravatarImage;
 import com.gitblit.wicket.panels.IconAjaxLink;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.ShockWaveComponent;
@@ -312,7 +312,7 @@
 					if (user == null) {
 						user = new UserModel(username);
 					}
-					item.add(new GravatarImage("participant", user.getDisplayName(),
+					item.add(new AvatarImage("participant", user.getDisplayName(),
 							user.emailAddress, null, 25, true));
 				}
 			};
@@ -519,6 +519,10 @@
 		 * TICKET METADATA
 		 */
 		add(new Label("ticketType", ticket.type.toString()));
+
+		add(new Label("priority", ticket.priority.toString()));
+		add(new Label("severity", ticket.severity.toString()));
+
 		if (StringUtils.isEmpty(ticket.topic)) {
 			add(new Label("ticketTopic").setVisible(false));
 		} else {
@@ -527,6 +531,8 @@
 			String safeTopic = app().xssFilter().relaxed(topic);
 			add(new Label("ticketTopic", safeTopic).setEscapeModelStrings(false));
 		}
+
+
 
 
 		/*
@@ -730,7 +736,7 @@
 		} else {
 			// permit user to comment
 			Fragment newComment = new Fragment("newComment", "newCommentFragment", this);
-			GravatarImage img = new GravatarImage("newCommentAvatar", user.username, user.emailAddress,
+			AvatarImage img = new AvatarImage("newCommentAvatar", user.username, user.emailAddress,
 					"gravatar-round", avatarWidth, true);
 			newComment.add(img);
 			CommentPanel commentPanel = new CommentPanel("commentPanel", user, ticket, null, TicketsPage.class);
@@ -746,7 +752,7 @@
 		if (currentPatchset == null) {
 			// no patchset available
 			RepositoryUrl repoUrl = getRepositoryUrl(user, repository);
-			boolean canPropose = repoUrl != null && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user);
+			boolean canPropose = repoUrl != null && repoUrl.hasPermission() && repoUrl.permission.atLeast(AccessPermission.CLONE) && !UserModel.ANONYMOUS.equals(user);
 			if (ticket.isOpen() && app().tickets().isAcceptingNewPatchsets(repository) && canPropose) {
 				// ticket & repo will accept a proposal patchset
 				// show the instructions for proposing a patchset
@@ -810,14 +816,14 @@
 				public void populateItem(final Item<RevCommit> item) {
 					RevCommit commit = item.getModelObject();
 					PersonIdent author = commit.getAuthorIdent();
-					item.add(new GravatarImage("authorAvatar", author.getName(), author.getEmailAddress(), null, 16, false));
+					item.add(new AvatarImage("authorAvatar", author.getName(), author.getEmailAddress(), null, 16, false));
 					item.add(new Label("author", commit.getAuthorIdent().getName()));
 					item.add(new LinkPanel("commitId", null, getShortObjectId(commit.getName()),
 							CommitPage.class, WicketUtils.newObjectParameter(repositoryName, commit.getName()), true));
 					item.add(new LinkPanel("diff", "link", getString("gb.diff"), CommitDiffPage.class,
 							WicketUtils.newObjectParameter(repositoryName, commit.getName()), true));
 					item.add(new Label("title", StringUtils.trimString(commit.getShortMessage(), Constants.LEN_SHORTLOG_REFS)));
-					item.add(WicketUtils.createDateLabel("commitDate", JGitUtils.getCommitDate(commit), GitBlitWebSession
+					item.add(WicketUtils.createDateLabel("commitDate", JGitUtils.getAuthorDate(commit), GitBlitWebSession
 							.get().getTimezone(), getTimeUtils(), false));
 					item.add(new DiffStatPanel("commitDiffStat", 0, 0, true));
 				}
@@ -981,12 +987,12 @@
 		UserModel commenter = app().users().getUserModel(entry.author);
 		if (commenter == null) {
 			// unknown user
-			container.add(new GravatarImage("changeAvatar", entry.author,
+			container.add(new AvatarImage("changeAvatar", entry.author,
 					entry.author, null, avatarSize, false).setVisible(avatarSize > 0));
 			container.add(new Label("changeAuthor", entry.author.toLowerCase()));
 		} else {
 			// known user
-			container.add(new GravatarImage("changeAvatar", commenter.getDisplayName(),
+			container.add(new AvatarImage("changeAvatar", commenter.getDisplayName(),
 					commenter.emailAddress, avatarSize > 24 ? "gravatar-round" : null,
 							avatarSize, true).setVisible(avatarSize > 0));
 			container.add(new LinkPanel("changeAuthor", null, commenter.getDisplayName(),
@@ -1425,6 +1431,12 @@
 				Fragment mergePanel = new Fragment("mergePanel", "alreadyMergedFragment", this);
 				mergePanel.add(new Label("mergeTitle", MessageFormat.format(getString("gb.patchsetAlreadyMerged"), ticket.mergeTo)));
 				return mergePanel;
+			} else if (MergeStatus.MISSING_INTEGRATION_BRANCH == mergeStatus) {
+				// target/integration branch is missing
+				Fragment mergePanel = new Fragment("mergePanel", "notMergeableFragment", this);
+				mergePanel.add(new Label("mergeTitle", MessageFormat.format(getString("gb.patchsetNotMergeable"), ticket.mergeTo)));
+				mergePanel.add(new Label("mergeMore", MessageFormat.format(getString("gb.missingIntegrationBranchMore"), ticket.mergeTo)));
+				return mergePanel;
 			} else {
 				// patchset can not be cleanly merged
 				Fragment mergePanel = new Fragment("mergePanel", "notMergeableFragment", this);
@@ -1503,7 +1515,7 @@
 	 */
 	protected RepositoryUrl getRepositoryUrl(UserModel user, RepositoryModel repository) {
 		HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
-		List<RepositoryUrl> urls = app().gitblit().getRepositoryUrls(req, user, repository);
+		List<RepositoryUrl> urls = app().services().getRepositoryUrls(req, user, repository);
 		if (ArrayUtils.isEmpty(urls)) {
 			return null;
 		}
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
index 3a3d977..c686d1f 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
@@ -31,17 +31,18 @@
 			<div class="hidden-phone">
 				<ul class="nav nav-list">
   					<li class="nav-header"><wicket:message key="gb.queries"></wicket:message></li>
-  					<li><a wicket:id="changesQuery"><i class="fa fa-code-fork"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
-					<li><a wicket:id="bugsQuery"><i class="fa fa-bug"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
-					<li><a wicket:id="enhancementsQuery"><i class="fa fa-magic"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
-					<li><a wicket:id="tasksQuery"><i class="fa fa-ticket"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
-					<li><a wicket:id="questionsQuery"><i class="fa fa-question"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+  					<li><a wicket:id="changesQuery"><i class="fa fa-code-fork fa-fw"></i> <wicket:message key="gb.proposalTickets"></wicket:message></a></li>
+					<li><a wicket:id="bugsQuery"><i class="fa fa-bug fa-fw"></i> <wicket:message key="gb.bugTickets"></wicket:message></a></li>
+					<li><a wicket:id="enhancementsQuery"><i class="fa fa-magic fa-fw"></i> <wicket:message key="gb.enhancementTickets"></wicket:message></a></li>
+					<li><a wicket:id="tasksQuery"><i class="fa fa-ticket fa-fw"></i> <wicket:message key="gb.taskTickets"></wicket:message></a></li>
+					<li><a wicket:id="questionsQuery"><i class="fa fa-question fa-fw"></i> <wicket:message key="gb.questionTickets"></wicket:message></a></li>
+					<li><a wicket:id="maintenanceQuery"><i class="fa fa-cogs fa-fw"></i> <wicket:message key="gb.maintenanceTickets"></wicket:message></a></li>
   					<li wicket:id="userDivider" class="divider"></li>
-  					<li><a wicket:id="createdQuery"><i class="fa fa-user"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
-  					<li><a wicket:id="watchedQuery"><i class="fa fa-eye"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
-  					<li><a wicket:id="mentionsQuery"><i class="fa fa-comment"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
+  					<li><a wicket:id="createdQuery"><i class="fa fa-user fa-fw"></i> <wicket:message key="gb.yourCreatedTickets"></wicket:message></a></li>
+  					<li><a wicket:id="watchedQuery"><i class="fa fa-eye fa-fw"></i> <wicket:message key="gb.yourWatchedTickets"></wicket:message></a></li>
+  					<li><a wicket:id="mentionsQuery"><i class="fa fa-comment fa-fw"></i> <wicket:message key="gb.mentionsMeTickets"></wicket:message></a></li>
   					<li class="divider"></li>
-  					<li><a wicket:id="resetQuery"><i class="fa fa-bolt"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
+  					<li><a wicket:id="resetQuery"><i class="fa fa-bolt fa-fw"></i> <wicket:message key="gb.reset"></wicket:message></a></li>
 				</ul>
 			</div>			
 			<div wicket:id="dynamicQueries" class="hidden-phone"></div>
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
index 658cdde..ecfed25 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -272,6 +272,16 @@
 						sortBy,
 						desc,
 						1)));
+		
+		add(new BookmarkablePageLink<Void>("maintenanceQuery", TicketsPage.class,
+				queryParameters(
+						Lucene.type.matches(TicketModel.Type.Maintenance.name()),
+						milestoneParam,
+						statiiParam,
+						assignedToParam,
+						sortBy,
+						desc,
+						1)));
 
 		add(new BookmarkablePageLink<Void>("resetQuery", TicketsPage.class,
 				queryParameters(
@@ -454,7 +464,11 @@
 		sortChoices.add(new TicketSort(getString("gb.sortLeastPatchsetRevisions"), Lucene.patchsets.name(), false));
 		sortChoices.add(new TicketSort(getString("gb.sortMostVotes"), Lucene.votes.name(), true));
 		sortChoices.add(new TicketSort(getString("gb.sortLeastVotes"), Lucene.votes.name(), false));
-
+		sortChoices.add(new TicketSort(getString("gb.sortHighestPriority"), Lucene.priority.name(), true));
+		sortChoices.add(new TicketSort(getString("gb.sortLowestPriority"), Lucene.priority.name(), false));
+		sortChoices.add(new TicketSort(getString("gb.sortHighestSeverity"), Lucene.severity.name(), true));
+		sortChoices.add(new TicketSort(getString("gb.sortLowestSeverity"), Lucene.severity.name(), false));
+		
 		TicketSort currentSort = sortChoices.get(0);
 		for (TicketSort ts : sortChoices) {
 			if (ts.sortBy.equals(sortBy) && desc == ts.desc) {
diff --git a/src/main/java/com/gitblit/wicket/pages/TreePage.java b/src/main/java/com/gitblit/wicket/pages/TreePage.java
index 9ddbecf..d7899dc 100644
--- a/src/main/java/com/gitblit/wicket/pages/TreePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TreePage.java
@@ -52,7 +52,7 @@
 
 		Repository r = getRepository();
 		RevCommit commit = getCommit();
-		List<PathModel> paths = JGitUtils.getFilesInPath(r, path, commit);
+		List<PathModel> paths = JGitUtils.getFilesInPath2(r, path, commit);
 
 		// tree page links
 		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
diff --git a/src/main/java/com/gitblit/wicket/pages/UserPage.java b/src/main/java/com/gitblit/wicket/pages/UserPage.java
index 8931d5e..ea68f25 100644
--- a/src/main/java/com/gitblit/wicket/pages/UserPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/UserPage.java
@@ -104,7 +104,7 @@
 		if (isMyProfile) {
 			addPreferences(user);
 
-			if (app().gitblit().isServingSSH()) {
+			if (app().services().isServingSSH()) {
 				// show the SSH key management tab
 				addSshKeys(user);
 			} else {
@@ -183,7 +183,8 @@
 				new Language("Norsk", "no"),
 				new Language("Język Polski", "pl"),
 				new Language("Português", "pt_BR"),
-				new Language("中文", "zh_CN"));
+				new Language("簡體中文", "zh_CN"),
+				new Language("正體中文", "zh_TW"));
 
 		Locale locale = user.getPreferences().getLocale();
 		if (locale == null) {
@@ -248,14 +249,16 @@
 				emailMeOnMyTicketChanges).setVisible(app().notifier().isSendingMail()));
 
 		List<Transport> availableTransports = new ArrayList<>();
-		if (app().gitblit().isServingSSH()) {
+		if (app().services().isServingSSH()) {
 			availableTransports.add(Transport.SSH);
 		}
-		if (app().gitblit().isServingHTTP()) {
-			availableTransports.add(Transport.HTTPS);
+		if (app().services().isServingHTTP()) {
 			availableTransports.add(Transport.HTTP);
 		}
-		if (app().gitblit().isServingGIT()) {
+		if (app().services().isServingHTTPS()) {
+			availableTransports.add(Transport.HTTPS);
+		}
+		if (app().services().isServingGIT()) {
 			availableTransports.add(Transport.GIT);
 		}
 
diff --git a/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
new file mode 100644
index 0000000..6157513
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/scripts/imgdiff.js
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2014 Tom <tw201207@gmail.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.
+ */
+(function($) {
+
+/**
+ * Sets up elem as a slider; returns an access object. Elem must be positioned!
+ * Note that the element may contain other elements; this is used for instance
+ * for the image diff overlay slider.
+ *
+ * The styling of the slider is to be done in CSS. Currently recognized options:
+ * - initial: <float> clipped to [0..1], default 0
+ * - handleClass: <string> to assign to the handle span element created.
+ * If no handleClass is specified, a very plain default style is assigned.
+ */
+function rangeSlider(elem, options) {
+	options = $.extend({ initial : 0 }, options || {});
+	options.initial = Math.min(1.0, Math.max(0, options.initial));
+	
+	var $elem = $(elem);
+	var $handle = $('<span></span>').css({ position: 'absolute', left: 0, cursor: 'ew-resize' });
+	var $root = $(document.documentElement);
+	var $doc = $(document);	
+	var lastRatio = options.initial;
+
+	/** Mousemove event handler to track the mouse and move the slider. Generates slider:pos events. */
+	function track(e) {
+		var pos = $elem.offset().left;
+		var width = $elem.innerWidth();
+		var handleWidth = $handle.outerWidth(false);
+		var range = width - handleWidth;
+		if (range <= 0) return;
+		var delta = Math.min(range, Math.max (0, e.pageX - pos - handleWidth / 2));
+		lastRatio = delta / range;
+		$handle.css('left', "" + (delta * 100 / width) + '%');
+		$elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] });
+	}
+
+	/** Mouseup event handler to stop mouse tracking. */
+	function end(e) {
+		$doc.off('mousemove', track);
+		$doc.off('mouseup', end);
+		$root.removeClass('no-select');
+	}
+
+    /** Snaps the slider to the given ratio and generates a slider:pos event with the new ratio. */
+	function setTo(ratio) {
+		var w = $elem.innerWidth();
+		if (w <= 0 || $elem.is(':hidden')) return;
+		lastRatio = Math.min( 1.0, Math.max(0, ratio));
+		$handle.css('left', "" + Math.max(0, 100 * (lastRatio * (w - $handle.outerWidth(false))) / w) + '%');
+		$elem.trigger('slider:pos', { ratio: lastRatio, handle: $handle[0] });
+	}
+	
+	/**
+	 * Moves the slider to the given ratio, clipped to [0..1], in duration milliseconds.
+	 * Generates slider:pos events during the animation. If duration <= 30, same as setTo.
+	 * Default duration is 500ms. If a callback is given, it's called once the animation
+	 * has completed.
+	 */
+	function moveTo(ratio, duration, callback) {
+		ratio = Math.min(1.0, Math.max(0, ratio));
+		if (ratio === lastRatio) {
+			if (typeof callback == 'function') callback();
+			return;
+		}
+		if (typeof duration == 'undefined') duration = 500;
+		if (duration <= 30) {
+			 // Cinema is 24 or 48 frames/sec, so 20-40ms per frame. Makes no sense to animate for such a short duration.
+			setTo(ratio);
+			if (typeof callback == 'function') callback();
+		} else {
+			var target = ratio * ($elem.innerWidth() - $handle.outerWidth(false));
+			if (ratio > lastRatio) target--; else target++;
+			$handle.stop().animate({left: target},
+				{ 'duration' : duration,
+				  'step' : function() {
+						lastRatio = Math.min(1.0, Math.max(0, $handle.position().left / ($elem.innerWidth() - $handle.outerWidth(false))));
+						$elem.trigger('slider:pos', { ratio : lastRatio, handle : $handle[0] });
+					},
+				  'complete' : function() { setTo(ratio); if (typeof callback == 'function') callback(); } // Ensure we have again a % value
+				}
+			);
+		}
+	}
+	
+	/**
+	 * As moveTo, but determines an appropriate duration in the range [0..maxDuration] on its own,
+	 * depending on the distance the handle would move. If no maxDuration is given it defaults
+	 * to 1500ms.
+	 */
+	function moveAuto(ratio, maxDuration, callback) {
+		if (typeof maxDuration == 'undefined') maxDuration = 1500;
+		var delta = ratio - lastRatio;
+		if (delta < 0) delta = -delta;
+		var speed = $elem.innerWidth() * delta * 2;
+		if (speed > maxDuration) speed = maxDuration;
+		moveTo(ratio, speed, callback);
+	}
+
+	/** Returns the current ratio. */
+	function getValue() {
+		return lastRatio;
+	}
+
+	$elem.append($handle);
+	if (options.handleClass) {
+		$handle.addClass(options.handleClass);
+	} else { // Provide a default style so that it is at least visible
+		$handle.css({ width: '10px', height: '10px', background: 'white', border: '1px solid black' });
+	}
+	if (options.initial) setTo(options.initial);
+
+	/** Install mousedown handler to start mouse tracking. */
+	$handle.on('mousedown', function(e) {
+		$root.addClass('no-select');
+		$doc.on('mousemove', track);
+		$doc.on('mouseup', end);
+		e.stopPropagation();
+		e.preventDefault();
+	});
+
+	return { setRatio: setTo, moveRatio: moveTo, 'moveAuto': moveAuto, getRatio: getValue, handle: $handle[0] };
+}
+
+function setup() {
+	$('.imgdiff-container').each(function() {
+		var $this = $(this);
+		var $overlaySlider = $this.find('.imgdiff-ovr-slider').first();
+		var $opacitySlider = $this.find('.imgdiff-opa-slider').first();
+		var overlayAccess = rangeSlider($overlaySlider, {handleClass: 'imgdiff-ovr-handle'});
+		var opacityAccess = rangeSlider($opacitySlider, {handleClass: 'imgdiff-opa-handle'});
+		var $img = $('#' + this.id.substr(this.id.indexOf('-')+1)); // Here we change opacity
+		var $div = $img.parent(); // This controls visibility: here we change width.
+		var blinking = false;
+		
+		$overlaySlider.on('slider:pos', function(e, data) {
+			var pos = $(data.handle).offset().left;
+			var imgLeft = $img.offset().left; // Global
+			var imgW = $img.outerWidth(true);
+			var imgOff = $img.position().left; // From left edge of $div
+			if (pos <= imgLeft) {
+				$div.width(0);
+			} else if (pos <= imgLeft + imgW) {
+				$div.width(pos - imgLeft + imgOff);
+			} else if ($div.width() < imgW + imgOff) {
+				$div.width(imgW + imgOff);
+			}
+		});
+		$overlaySlider.css('cursor', 'pointer');
+		$overlaySlider.on('mousedown', function(e) {
+			var newRatio = (e.pageX - $overlaySlider.offset().left) / $overlaySlider.innerWidth();
+			var oldRatio = overlayAccess.getRatio();
+			if (newRatio !== oldRatio) {
+				overlayAccess.moveAuto(newRatio);
+			}
+		});
+		
+		var autoShowing = false;
+		$opacitySlider.on('slider:pos', function(e, data) {
+			if ($div.width() <= 0 && !blinking) {
+				// Make old image visible in a nice way, *then* adjust opacity
+				autoShowing = true;
+				overlayAccess.moveAuto(1.0, 500, function() {
+					$img.stop().animate(
+						{opacity: 1.0 - opacityAccess.getRatio()},
+						{duration: 400,
+						 complete: function () {
+							// In case the opacity handle was moved while we were trying to catch up
+							$img.css('opacity', 1.0 - opacityAccess.getRatio());
+							autoShowing = false;
+						 }
+						}
+					);
+				});
+			} else if (!autoShowing) {
+				$img.css('opacity', 1.0 - data.ratio);
+			}
+		});
+		$opacitySlider.on('click', function(e) {
+			var newRatio = (e.pageX - $opacitySlider.offset().left) / $opacitySlider.innerWidth();
+			var oldRatio = opacityAccess.getRatio();
+			if (newRatio !== oldRatio) {
+				if ($div.width() <= 0) {
+					overlayAccess.moveRatio(1.0, 500, function() {opacityAccess.moveAuto(newRatio);}); // Make old image visible in a nice way
+				} else {
+					opacityAccess.moveAuto(newRatio)
+				}
+			}
+			e.preventDefault();
+		});
+			
+		// Blinking before and after images is a good way for the human eye to catch differences.
+		var $blinker = $this.find('.imgdiff-blink');
+		var initialOpacity = null;
+		$blinker.on('click', function(e) {
+			if (blinking) {
+				window.clearTimeout(blinking);
+				$blinker.children('img').first().css('border', '1px solid transparent');
+				opacityAccess.setRatio(initialOpacity);
+				blinking = null;
+			} else {
+				$blinker.children('img').first().css('border', '1px solid #AAA');
+				initialOpacity = opacityAccess.getRatio();
+				var currentOpacity = 1.0;
+				function blink() {
+					opacityAccess.setRatio(currentOpacity);
+					currentOpacity = 1.0 - currentOpacity;
+					// Keep frequeny below 2Hz (i.e., delay above 500ms)
+					blinking = window.setTimeout(blink, 600);
+				}
+				if ($div.width() <= 0) {
+					overlayAccess.moveRatio(1.0, 500, blink);
+				} else {
+					blink();
+				}
+			}
+			e.preventDefault();
+		});
+		
+		// Subtracting before and after images is another good way to detect differences. Result will be
+		// black where identical.		
+		if (typeof $img[0].style.mixBlendMode != 'undefined') {
+			// Feature test: does the browser support the mix-blend-mode CSS property from the Compositing 
+			// and Blending Level 1 spec (http://dev.w3.org/fxtf/compositing-1/#mix-blend-mode )?
+			// As of 2014-11, only Firefox >= 32 and Safari >= 7.1 support this. Other browsers will have to
+			// make do with the blink comparator only.
+			var $sub = $this.find('.imgdiff-subtract');
+			$sub.css('display', 'inline-block');
+			$sub.on('click', function (e) {
+				var curr = $img.css('mix-blend-mode');
+				if (curr != 'difference') {
+					curr = 'difference';
+					$sub.children('img').first().css('border', '1px solid #AAA');
+					if ($div.width() <= 0) overlayAccess.moveRatio(1.0, 500);
+					opacityAccess.setRatio(0);
+				} else {
+					curr = 'normal';
+					$sub.children('img').first().css('border', '1px solid transparent');
+					
+				}
+				$img.css('mix-blend-mode', curr);
+				e.preventDefault();
+			});
+		}
+	});
+}
+
+$(setup); // Run on jQuery's dom-ready
+
+})(jQuery);
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java b/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
index d8dcdce..2c88024 100644
--- a/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/AccessPolicyPanel.java
@@ -26,8 +26,8 @@
 import org.apache.wicket.markup.html.list.ListItem;
 import org.apache.wicket.markup.html.list.ListView;
 import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
 
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.AuthorizationControl;
@@ -50,8 +50,6 @@
 	private final AjaxFormChoiceComponentUpdatingBehavior callback;
 
 	private RadioGroup<AccessPolicy> policiesGroup;
-
-	private IModel<Boolean> allowForks;
 
 	public AccessPolicyPanel(String wicketId, RepositoryModel repository) {
 		this(wicketId, repository, null);
@@ -146,13 +144,12 @@
 		}
 		add(policiesGroup);
 
-		allowForks = Model.of(app().settings().getBoolean(Keys.web.allowForking, true));
-		if (allowForks.getObject()) {
+		if (app().settings().getBoolean(Keys.web.allowForking, true)) {
 			Fragment fragment = new Fragment("allowForks", "allowForksFragment", this);
 			fragment.add(new BooleanOption("allowForks",
 				getString("gb.allowForks"),
 				getString("gb.allowForksDescription"),
-				allowForks));
+				new PropertyModel<Boolean>(repository, "allowForks")));
 			add(fragment);
 		} else {
 			add(new Label("allowForks").setVisible(false));
@@ -165,7 +162,6 @@
 		AccessPolicy policy = policiesGroup.getModelObject();
 		repository.authorizationControl = policy.control;
 		repository.accessRestriction = policy.type;
-		repository.allowForks = allowForks.getObject();
 	}
 
 	@Override
diff --git a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
index 35513bb..062df84 100644
--- a/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ActivityPanel.java
@@ -76,7 +76,7 @@
 								.getWhen(), getTimeZone(), getTimeUtils()));
 
 						// avatar
-						commitItem.add(new GravatarImage("avatar", commit.getAuthorIdent(), 40));
+						commitItem.add(new AvatarImage("avatar", commit.getAuthorIdent(), 40));
 
 						// merge icon
 						if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.html b/src/main/java/com/gitblit/wicket/panels/AvatarImage.html
similarity index 100%
rename from src/main/java/com/gitblit/wicket/panels/GravatarImage.html
rename to src/main/java/com/gitblit/wicket/panels/AvatarImage.html
diff --git a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java b/src/main/java/com/gitblit/wicket/panels/AvatarImage.java
similarity index 67%
rename from src/main/java/com/gitblit/wicket/panels/GravatarImage.java
rename to src/main/java/com/gitblit/wicket/panels/AvatarImage.java
index e415757..a562042 100644
--- a/src/main/java/com/gitblit/wicket/panels/GravatarImage.java
+++ b/src/main/java/com/gitblit/wicket/panels/AvatarImage.java
@@ -17,9 +17,9 @@
 
 import org.eclipse.jgit.lib.PersonIdent;
 
+import com.gitblit.AvatarGenerator;
 import com.gitblit.Keys;
 import com.gitblit.models.UserModel;
-import com.gitblit.utils.ActivityUtils;
 import com.gitblit.wicket.ExternalImage;
 import com.gitblit.wicket.WicketUtils;
 
@@ -29,36 +29,31 @@
  * @author James Moger
  *
  */
-public class GravatarImage extends BasePanel {
+public class AvatarImage extends BasePanel {
 
 	private static final long serialVersionUID = 1L;
 
-	public GravatarImage(String id, PersonIdent person) {
+	public AvatarImage(String id, PersonIdent person) {
 		this(id, person, 0);
 	}
 
-	public GravatarImage(String id, PersonIdent person, int width) {
+	public AvatarImage(String id, PersonIdent person, int width) {
 		this(id, person.getName(), person.getEmailAddress(), "gravatar", width, true);
 	}
 
-	public GravatarImage(String id, PersonIdent person, String cssClass, int width, boolean identicon) {
+	public AvatarImage(String id, PersonIdent person, String cssClass, int width, boolean identicon) {
 		this(id, person.getName(), person.getEmailAddress(), cssClass, width, identicon);
 	}
 
-	public GravatarImage(String id, UserModel user, String cssClass, int width, boolean identicon) {
+	public AvatarImage(String id, UserModel user, String cssClass, int width, boolean identicon) {
 		this(id, user.getDisplayName(), user.emailAddress, cssClass, width, identicon);
 	}
 
-	public GravatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean identicon) {
+	public AvatarImage(String id, String username, String emailaddress, String cssClass, int width, boolean identicon) {
 		super(id);
 
-		String email = emailaddress == null ? username.toLowerCase() : emailaddress.toLowerCase();
-		String url;
-		if (identicon) {
-			url = ActivityUtils.getGravatarIdenticonUrl(email, width);
-		} else {
-			url = ActivityUtils.getGravatarThumbnailUrl(email, width);
-		}
+		AvatarGenerator avatarGenerator = app().runtime().getInjector().getInstance(AvatarGenerator.class);
+		String url = avatarGenerator.getURL(username, emailaddress, identicon, width);
 		ExternalImage image = new ExternalImage("image", url);
 		if (cssClass != null) {
 			WicketUtils.setCssClass(image, cssClass);
diff --git a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
index 4bf00f8..7a564aa 100644
--- a/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/BranchesPanel.java
@@ -114,11 +114,13 @@
 			public void populateItem(final Item<RefModel> item) {
 				final RefModel entry = item.getModelObject();
 
+				PageParameters shortUniqRef = WicketUtils.newObjectParameter(model.name,
+						Repository.shortenRefName(entry.getName()));
+
 				item.add(WicketUtils.createDateLabel("branchDate", entry.getDate(), getTimeZone(), getTimeUtils()));
 
 				item.add(new LinkPanel("branchName", "list name", StringUtils.trimString(
-						entry.displayName, 28), LogPage.class, WicketUtils.newObjectParameter(
-						model.name, entry.getName())));
+						entry.displayName, 28), LogPage.class, shortUniqRef));
 
 				String author = entry.getAuthorIdent().getName();
 				LinkPanel authorLink = new LinkPanel("branchAuthor", "list", author,
@@ -131,8 +133,7 @@
 				String shortMessage = entry.getShortMessage();
 				String trimmedMessage = StringUtils.trimString(shortMessage, Constants.LEN_SHORTLOG);
 				LinkPanel shortlog = new LinkPanel("branchLog", "list subject", trimmedMessage,
-						CommitPage.class, WicketUtils.newObjectParameter(model.name,
-								entry.getName()));
+						CommitPage.class, shortUniqRef);
 				if (!shortMessage.equals(trimmedMessage)) {
 					shortlog.setTooltip(shortMessage);
 				}
@@ -140,27 +141,22 @@
 
 				if (maxCount <= 0) {
 					Fragment fragment = new Fragment("branchLinks", showDelete? "branchPageAdminLinks" : "branchPageLinks", this);
-					fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
-							.newObjectParameter(model.name, entry.getName())));
-					fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
-							.newObjectParameter(model.name, entry.getName())));
+					fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, shortUniqRef));
+					fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, shortUniqRef));
 					String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null);
-					fragment.add(new ExternalLink("raw",  rawUrl));
-					fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
-							WicketUtils.newObjectParameter(model.name, entry.getName())));
+					fragment.add(new ExternalLink("raw", rawUrl));
+					fragment.add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class, shortUniqRef));
 					fragment.add(new ExternalLink("syndication", SyndicationServlet.asLink(
 							getRequest().getRelativePathPrefixToContextRoot(), model.name,
-							entry.getName(), 0)));
+							Repository.shortenRefName(entry.getName()), 0)));
 					if (showDelete) {
 						fragment.add(createDeleteBranchLink(model, entry));
 					}
 					item.add(fragment);
 				} else {
 					Fragment fragment = new Fragment("branchLinks", "branchPanelLinks", this);
-					fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, WicketUtils
-							.newObjectParameter(model.name, entry.getName())));
-					fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, WicketUtils
-							.newObjectParameter(model.name, entry.getName())));
+					fragment.add(new BookmarkablePageLink<Void>("log", LogPage.class, shortUniqRef));
+					fragment.add(new BookmarkablePageLink<Void>("tree", TreePage.class, shortUniqRef));
 					String rawUrl = RawServlet.asLink(getContextUrl(), model.name, Repository.shortenRefName(entry.getName()), null);
 					fragment.add(new ExternalLink("raw",  rawUrl));
 					item.add(fragment);
diff --git a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
index eb75750..249cd4a 100644
--- a/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/CommitHeaderPanel.java
@@ -44,6 +44,6 @@
 		add(new Label("commitid", c.getName()));
 		add(new Label("author", c.getAuthorIdent().getName()));
 		add(WicketUtils.createDateLabel("date", c.getAuthorIdent().getWhen(), getTimeZone(), getTimeUtils()));
-		add(new GravatarImage("authorAvatar", c.getAuthorIdent()));
+		add(new AvatarImage("authorAvatar", c.getAuthorIdent()));
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
index decfda5..0c80f99 100644
--- a/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/DigestsPanel.java
@@ -166,7 +166,7 @@
 				} else if (isTag) {
 					// link to tag
 					logItem.add(new LinkPanel("refChanged", null, shortRefName,
-							TagPage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+							TagPage.class, WicketUtils.newObjectParameter(change.repository, shortRefName)));
 				} else if (isTicket) {
 					// link to ticket
 					logItem.add(new LinkPanel("refChanged", null, shortRefName,
@@ -174,7 +174,7 @@
 				} else {
 					// link to tree
 					logItem.add(new LinkPanel("refChanged", null, shortRefName,
-						TreePage.class, WicketUtils.newObjectParameter(change.repository, fullRefName)));
+						TreePage.class, WicketUtils.newObjectParameter(change.repository, shortRefName)));
 				}
 
 				// to/from/etc
@@ -216,7 +216,7 @@
 						final RepositoryCommit commit = commitItem.getModelObject();
 
 						// author gravatar
-						commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
+						commitItem.add(new AvatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
 
 						// merge icon
 						if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
index 4433b04..e4ce5ce 100644
--- a/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
+++ b/src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java
@@ -125,7 +125,15 @@
 			item.t = getTimeUtils().timeAgo(repo.lastChange);
 			item.d = df.format(repo.lastChange);
 			item.c = StringUtils.getColor(StringUtils.stripDotGit(repo.name));
-			item.wc = repo.isBare ? 0 : 1;
+			if (!repo.isBare) {
+				item.y = 3;
+			} else if (repo.isMirror) {
+				item.y = 2;
+			} else if (repo.isFork()) {
+				item.y = 1;
+			} else {
+				item.y = 0;
+			}
 			list.add(item);
 		}
 
@@ -147,6 +155,6 @@
 		String i; // information/description
 		long s;   // stars
 		String c; // html color
-		int wc;   // working copy: 1 = true, 0 = false
+		int y;    // type: 0 = normal, 1 = fork, 2 = mirror, 3 = clone
 	}
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
index e1706a0..a3f127b 100644
--- a/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/HistoryPanel.java
@@ -116,7 +116,7 @@
 					}
 				} catch (Exception e) {
 				} finally {
-					tw.release();
+					tw.close();
 				}
 			}
 		}
@@ -160,7 +160,7 @@
 			@Override
 			public void populateItem(final Item<RevCommit> item) {
 				final RevCommit entry = item.getModelObject();
-				final Date date = JGitUtils.getCommitDate(entry);
+				final Date date = JGitUtils.getAuthorDate(entry);
 
 				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
 
diff --git a/src/main/java/com/gitblit/wicket/panels/LogPanel.java b/src/main/java/com/gitblit/wicket/panels/LogPanel.java
index 16fc746..e9d240d 100644
--- a/src/main/java/com/gitblit/wicket/panels/LogPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/LogPanel.java
@@ -109,7 +109,7 @@
 			@Override
 			public void populateItem(final Item<RevCommit> item) {
 				final RevCommit entry = item.getModelObject();
-				final Date date = JGitUtils.getCommitDate(entry);
+				final Date date = JGitUtils.getAuthorDate(entry);
 				final boolean isMerge = entry.getParentCount() > 1;
 
 				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
index 33345a0..88de3b4 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
@@ -24,13 +24,28 @@
 		<p class="originRepository" style="margin-left:20px;" ><wicket:message key="gb.forkedFrom">[forked from]</wicket:message> <span wicket:id="originRepository">[origin repository]</span></p>
 	</wicket:fragment>
 
+    <wicket:fragment wicket:id="repoIconFragment">
+        <span class="mega-octicon octicon-repo"></span>
+    </wicket:fragment>
+
+    <wicket:fragment wicket:id="mirrorIconFragment">
+        <span class="mega-octicon octicon-mirror"></span>
+    </wicket:fragment>
+
+    <wicket:fragment wicket:id="forkIconFragment">
+        <span class="mega-octicon octicon-repo-forked"></span>
+    </wicket:fragment>
+        
+    <wicket:fragment wicket:id="cloneIconFragment">
+        <span class="mega-octicon octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+    </wicket:fragment>
+
 	<div>
 		<div style="padding-top:15px;padding-bottom:15px;margin-right:15px;">
 			<div class="pull-right" style="text-align:right;padding-right:15px;">
 				<span wicket:id="repositoryLinks"></span>
 				<div>
 					<img class="inlineIcon" wicket:id="sparkleshareIcon" />
-					<img class="inlineIcon" wicket:id="mirrorIcon" />
 					<img class="inlineIcon" wicket:id="frozenIcon" />
 					<img class="inlineIcon" wicket:id="federatedIcon" />
         						
@@ -42,11 +57,15 @@
 			</div>	
 			
 			<div class="pageTitle" style="border:0px;">
-				<div>
-					<span class="repositorySwatch" wicket:id="repositorySwatch"></span>
-					<span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
-				</div>
-				<span wicket:id="originRepository">[origin repository]</span>
+                <div style="display:inline-block;vertical-align:top;padding-right:5px;">
+                    <span wicket:id="repoIcon"></span>
+                </div>
+                <div style="display:inline-block;">
+                    <div>
+					   <span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
+                    </div>
+                    <span wicket:id="originRepository">[origin repository]</span>
+				</div>				
 			</div>
 			
 			<div style="padding-left:20px;">
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
index 8630d20..efcb1cb 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -51,23 +51,26 @@
 		final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
 		final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true);
 
-		// repository swatch
-		Component swatch;
-		if (entry.isBare) {
-			swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
-		} else {
-			swatch = new Label("repositorySwatch", "!");
-			WicketUtils.setHtmlTooltip(swatch, localizer.getString("gb.workingCopyWarning", parent));
-		}
-		WicketUtils.setCssBackground(swatch, entry.toString());
-		add(swatch);
-		swatch.setVisible(showSwatch);
-
 		PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
 		add(new LinkPanel("repositoryName", "list", StringUtils.getRelativePath(entry.projectPath,
 				StringUtils.stripDotGit(entry.name)), SummaryPage.class, pp));
 		add(new Label("repositoryDescription", entry.description).setVisible(!StringUtils
 				.isEmpty(entry.description)));
+
+		Fragment iconFragment;
+		if (entry.isMirror) {
+			iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);
+		} else if (entry.isFork()) {
+			iconFragment = new Fragment("repoIcon", "forkIconFragment", this);
+		} else if (entry.isBare) {
+			iconFragment = new Fragment("repoIcon", "repoIconFragment", this);
+		} else {
+			iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);
+		}
+		if (showSwatch) {
+			WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));
+		}
+		add(iconFragment);
 
 		if (StringUtils.isEmpty(entry.originRepository)) {
 			add(new Label("originRepository").setVisible(false));
@@ -84,13 +87,7 @@
 			add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
 		}
 
-		if (entry.isMirror) {
-			add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png", localizer.getString("gb.isMirror", parent)));
-		} else {
-			add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
-		}
-
-		if (entry.isFrozen) {
+		if (!entry.isMirror && entry.isFrozen) {
 			add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", localizer.getString("gb.isFrozen", parent)));
 		} else {
 			add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
@@ -161,6 +158,7 @@
 			add(new Label("repositorySize", localizer.getString("gb.empty", parent)).setEscapeModelStrings(false));
 		}
 
-		add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
+		add(new ExternalLink("syndication", SyndicationServlet.asLink(getRequest()
+				.getRelativePathPrefixToContextRoot(), entry.name, null, 0)));
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
index baefc6b..2235fd3 100644
--- a/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ReflogPanel.java
@@ -271,7 +271,7 @@
 						final RepositoryCommit commit = commitItem.getModelObject();
 
 						// author gravatar
-						commitItem.add(new GravatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
+						commitItem.add(new AvatarImage("commitAuthor", commit.getAuthorIdent(), null, 16, false));
 
 						// merge icon
 						if (commit.getParentCount() > 1) {
diff --git a/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
index f37cc2a..447e178 100644
--- a/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java
@@ -145,7 +145,7 @@
 					}
 
 					Fragment userFragment = new Fragment("registrant", "userRegistrant", RegistrantPermissionsPanel.this);
-					userFragment.add(new GravatarImage("userAvatar", ident, 20));
+					userFragment.add(new AvatarImage("userAvatar", ident, 20));
 					userFragment.add(new Label("userName", entry.registrant));
 					item.add(userFragment);
 				} else {
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
index e2e7b72..2de52b0 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.html
@@ -17,6 +17,22 @@
        		</tr>
     	</tbody>
 	</table>
+
+    <wicket:fragment wicket:id="repoIconFragment">
+        <span class="octicon octicon-centered octicon-repo"></span>
+    </wicket:fragment>
+
+    <wicket:fragment wicket:id="mirrorIconFragment">
+        <span class="octicon octicon-centered octicon-mirror"></span>
+    </wicket:fragment>
+
+    <wicket:fragment wicket:id="forkIconFragment">
+        <span class="octicon octicon-centered octicon-repo-forked"></span>
+    </wicket:fragment>
+        
+    <wicket:fragment wicket:id="cloneIconFragment">
+        <span class="octicon octicon-centered octicon-repo-push" wicket:message="title:gb.workingCopyWarning"></span>
+    </wicket:fragment>
 	
 	<wicket:fragment wicket:id="adminLinks">
 		<!-- page nav links -->	
@@ -76,10 +92,10 @@
 	</wicket:fragment>
 		
 	<wicket:fragment wicket:id="repositoryRow">
-        <td class="left" style="padding-left:3px;" ><b><span class="repositorySwatch" wicket:id="repositorySwatch"></span></b> <span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
+        <td class="left" style="padding-left:3px;" ><span wicket:id="repoIcon"></span><span style="padding-left:3px;" wicket:id="repositoryName">[repository name]</span></td>
         <td class="hidden-phone"><span class="list" wicket:id="repositoryDescription">[repository description]</span></td>
         <td class="hidden-tablet hidden-phone author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
-        <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="mirrorIcon" /><img class="inlineIcon" wicket:id="forkIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+        <td class="hidden-phone" style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="sparkleshareIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="federatedIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
         <td><span wicket:id="repositoryLastChange">[last change]</span></td>
         <td class="rightAlign hidden-phone" style="text-align: right;padding-right:15px;"><span style="font-size:0.8em;" wicket:id="repositorySize">[repository size]</span></td>
 	</wicket:fragment>
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
index 8573e1a..c3f0709 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -24,7 +24,6 @@
 import java.util.List;
 import java.util.Map;
 
-import org.apache.wicket.Component;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
@@ -183,23 +182,27 @@
 				Fragment row = new Fragment("rowContent", "repositoryRow", this);
 				item.add(row);
 
+				// show colored repository type icon
+				Fragment iconFragment;
+				if (entry.isMirror) {
+					iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);
+				} else if (entry.isFork()) {
+					iconFragment = new Fragment("repoIcon", "forkIconFragment", this);
+				} else if (entry.isBare) {
+					iconFragment = new Fragment("repoIcon", "repoIconFragment", this);
+				} else {
+					iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);
+				}
+				if (showSwatch) {
+					WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));
+				}
+				row.add(iconFragment);
+
 				// try to strip group name for less cluttered list
 				String repoName = entry.toString();
 				if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {
 					repoName = repoName.substring(currGroupName.length() + 1);
 				}
-
-				// repository swatch
-				Component swatch;
-				if (entry.isBare){
-					swatch = new Label("repositorySwatch", "&nbsp;").setEscapeModelStrings(false);
-				} else {
-					swatch = new Label("repositorySwatch", "!");
-					WicketUtils.setHtmlTooltip(swatch, getString("gb.workingCopyWarning"));
-				}
-				WicketUtils.setCssBackground(swatch, entry.toString());
-				row.add(swatch);
-				swatch.setVisible(showSwatch);
 
 				if (linksActive) {
 					Class<? extends BasePage> linkPage = SummaryPage.class;
@@ -228,21 +231,7 @@
 					row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));
 				}
 
-				if (entry.isMirror) {
-					row.add(WicketUtils.newImage("mirrorIcon", "mirror_16x16.png",
-							getString("gb.isMirror")));
-				} else {
-					row.add(WicketUtils.newClearPixel("mirrorIcon").setVisible(false));
-				}
-
-				if (entry.isFork()) {
-					row.add(WicketUtils.newImage("forkIcon", "commit_divide_16x16.png",
-							getString("gb.isFork")));
-				} else {
-					row.add(WicketUtils.newClearPixel("forkIcon").setVisible(false));
-				}
-
-				if (entry.isFrozen) {
+				if (!entry.isMirror && entry.isFrozen) {
 					row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
 							getString("gb.isFrozen")));
 				} else {
@@ -255,24 +244,30 @@
 				} else {
 					row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
 				}
-				switch (entry.accessRestriction) {
-				case NONE:
-					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-					break;
-				case PUSH:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
-							accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case CLONE:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
-							accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case VIEW:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
-							accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				default:
-					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+
+				if (entry.isMirror) {
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
+							getString("gb.isMirror")));
+				} else {
+					switch (entry.accessRestriction) {
+					case NONE:
+						row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+						break;
+					case PUSH:
+						row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+								accessRestrictionTranslations.get(entry.accessRestriction)));
+						break;
+					case CLONE:
+						row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+								accessRestrictionTranslations.get(entry.accessRestriction)));
+						break;
+					case VIEW:
+						row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+								accessRestrictionTranslations.get(entry.accessRestriction)));
+						break;
+					default:
+						row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+					}
 				}
 
 				String owner = "";
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
index 938226a..207f125 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -80,11 +80,11 @@
 
 		HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
 
-		List<RepositoryUrl> repositoryUrls = app().gitblit().getRepositoryUrls(req, user, repository);
+		List<RepositoryUrl> repositoryUrls = app().services().getRepositoryUrls(req, user, repository);
 		// grab primary url from the top of the list
 		primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
 
-		boolean canClone = primaryUrl != null && ((primaryUrl.permission == null) || primaryUrl.permission.atLeast(AccessPermission.CLONE));
+		boolean canClone = primaryUrl != null && (!primaryUrl.hasPermission() || primaryUrl.permission.atLeast(AccessPermission.CLONE));
 
 		if (repositoryUrls.size() == 0 || !canClone) {
 			// no urls, nothing to show.
@@ -145,7 +145,7 @@
 					fragment.add(content);
 					item.add(fragment);
 
-					Label permissionLabel = new Label("permission", repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+					Label permissionLabel = new Label("permission", repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
 					WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
 					String tooltip = getProtocolPermissionDescription(repository, repoUrl);
 					WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
@@ -165,7 +165,7 @@
 		if (repository.isMirror) {
 			urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",
 					getString("gb.isMirror")));
-		} else if (app().gitblit().isServingRepositories()) {
+		} else if (app().services().isServingRepositories()) {
 			switch (repository.accessRestriction) {
 			case NONE:
 				urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
@@ -201,7 +201,7 @@
 
 		urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
 
-		Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.isExternal() ? externalPermission : primaryUrl.permission.toString());
+		Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.hasPermission() ? primaryUrl.permission.toString() : externalPermission);
 		String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
 		WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
 		urlPanel.add(permissionLabel);
@@ -234,8 +234,8 @@
 				// filter the urls for the client app
 				List<RepositoryUrl> urls = new ArrayList<RepositoryUrl>();
 				for (RepositoryUrl repoUrl : repositoryUrls) {
-					if (clientApp.minimumPermission == null || repoUrl.permission == null) {
-						// no minimum permission or external permissions, assume it is satisfactory
+					if (clientApp.minimumPermission == null || !repoUrl.hasPermission()) {
+						// no minimum permission or untracked permissions, assume it is satisfactory
 						if (clientApp.supportsTransport(repoUrl.url)) {
 							urls.add(repoUrl);
 						}
@@ -339,7 +339,7 @@
 	}
 
 	protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
-		Label permissionLabel = new Label(wicketId, repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+		Label permissionLabel = new Label(wicketId, repoUrl.hasPermission() ? repoUrl.permission.toString() : externalPermission);
 		WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
 		String tooltip = getProtocolPermissionDescription(repository, repoUrl);
 		WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
@@ -369,18 +369,7 @@
 			RepositoryUrl repoUrl) {
 		if (!urlPermissionsMap.containsKey(repoUrl.url)) {
 			String note;
-			if (repoUrl.isExternal()) {
-				String protocol;
-				int protocolIndex = repoUrl.url.indexOf("://");
-				if (protocolIndex > -1) {
-					// explicit protocol specified
-					protocol = repoUrl.url.substring(0, protocolIndex);
-				} else {
-					// implicit SSH url
-					protocol = "ssh";
-				}
-				note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
-			} else {
+			if (repoUrl.hasPermission()) {
 				note = null;
 				String key;
 				switch (repoUrl.permission) {
@@ -411,6 +400,17 @@
 					String description = MessageFormat.format(pattern, repoUrl.permission.toString());
 					note = description;
 				}
+			} else {
+				String protocol;
+				int protocolIndex = repoUrl.url.indexOf("://");
+				if (protocolIndex > -1) {
+					// explicit protocol specified
+					protocol = repoUrl.url.substring(0, protocolIndex);
+				} else {
+					// implicit SSH url
+					protocol = "ssh";
+				}
+				note = MessageFormat.format(getString("gb.externalPermissions"), protocol);
 			}
 			urlPermissionsMap.put(repoUrl.url, note);
 		}
diff --git a/src/main/java/com/gitblit/wicket/panels/SearchPanel.java b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
index 5d0b2de..09322bc 100644
--- a/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/SearchPanel.java
@@ -86,7 +86,7 @@
 			@Override
 			public void populateItem(final Item<RevCommit> item) {
 				final RevCommit entry = item.getModelObject();
-				final Date date = JGitUtils.getCommitDate(entry);
+				final Date date = JGitUtils.getAuthorDate(entry);
 
 				item.add(WicketUtils.createDateLabel("commitDate", date, getTimeZone(), getTimeUtils()));
 
diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
index ff68929..2cce7b1 100644
--- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.html
@@ -15,16 +15,18 @@
 				<img style="vertical-align: middle; border: 1px solid #888; background-color: white;" src="users_16x16.png"/>
 				<wicket:message key="gb.teams">[teams]</wicket:message>
 			</th>
+			<th class="hidden-phone" style="width:140px;"><wicket:message key="gb.type">[type]</wicket:message></th>
 			<th class="hidden-phone" style="width:140px;"><wicket:message key="gb.teamMembers">[team members]</wicket:message></th>
 			<th class="hidden-phone" style="width:100px;"><wicket:message key="gb.repositories">[repositories]</wicket:message></th>
 			<th style="width:80px;" class="right"></th>
 		</tr>
-		<tbody>		
+		<tbody>
        		<tr wicket:id="teamRow">
        			<td class="left" ><div class="list" wicket:id="teamname">[teamname]</div></td>
+       			<td class="hidden-phone left" ><span style="font-size: 0.8em;" wicket:id="accountType">[account type]</span></td>
        			<td class="hidden-phone left" ><div class="list" wicket:id="members">[members]</div></td>
        			<td class="hidden-phone left" ><div class="list" wicket:id="repositories">[repositories]</div></td>
-       			<td class="rightAlign"><span wicket:id="teamLinks"></span></td>      			
+       			<td class="rightAlign"><span wicket:id="teamLinks"></span></td>
        		</tr>
     	</tbody>
 	</table>
diff --git a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
index c1e1a43..7f3fd9a 100644
--- a/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/TeamsPanel.java
@@ -60,6 +60,7 @@
 						EditTeamPage.class, WicketUtils.newTeamnameParameter(entry.name));
 				WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry.name);
 				item.add(editLink);
+				item.add(new Label("accountType", entry.accountType.name()));
 				item.add(new Label("members", entry.users.size() > 0 ? ("" + entry.users.size())
 						: ""));
 				item.add(new Label("repositories",
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
index 30f5036..659baea 100644
--- a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
@@ -30,6 +30,9 @@
         	<td class="hidden-phone ticket-list-state">
        			<i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
         	</td>
+        	<td class="ticket-list-priority">
+       			<div wicket:id="priority"></div>
+        	</td>
         	<td class="ticket-list-state">
        			<div wicket:id="status"></div>
         	</td>
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
index cc0b57a..1fbe87c 100644
--- a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
@@ -83,7 +83,10 @@
 					item.add(new Label("ticketsLink").setVisible(false));
 				}
 
-				item.add(TicketsUI.getStateIcon("state", ticket.type, ticket.status));
+				Label icon = TicketsUI.getStateIcon("state", ticket.type, ticket.status, ticket.severity);
+				WicketUtils.addCssClass(icon, TicketsUI.getSeverityClass(ticket.severity));
+				item.add(icon);
+
 				item.add(new Label("id", "" + ticket.number));
 				UserModel creator = app().users().getUserModel(ticket.createdBy);
 				if (creator != null) {
@@ -153,7 +156,7 @@
 					if (responsible == null) {
 						responsible = new UserModel(ticket.responsible);
 					}
-					GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
+					AvatarImage avatar = new AvatarImage("responsible", responsible.getDisplayName(),
 							responsible.emailAddress, null, 16, true);
 					avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
 					item.add(avatar);
@@ -167,6 +170,11 @@
 				// watching indicator
 				item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
 
+				// priority indicator
+				Label priorityIcon = TicketsUI.getPriorityIcon("priority", ticket.priority);
+				WicketUtils.addCssClass(priorityIcon, TicketsUI.getPriorityClass(ticket.priority));
+				item.add(priorityIcon.setVisible(true));
+
 				// status indicator
 				String css = TicketsUI.getLozengeClass(ticket.status, true);
 				Label l = new Label("status", ticket.status.toString());
diff --git a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
index 2bf5ee7..063889b 100644
--- a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
@@ -25,7 +25,7 @@
 
 	public UserTitlePanel(String wicketId, UserModel user, String title) {
 		super(wicketId);
-		add(new GravatarImage("userGravatar", user, "gravatar", 36, false));
+		add(new AvatarImage("userGravatar", user, "gravatar", 36, false));
 		add(new Label("userDisplayName", user.getDisplayName()));
 		add(new Label("userTitle", title));
 	}
diff --git a/src/main/java/login_zh_TW.mkd b/src/main/java/login_zh_TW.mkd
new file mode 100644
index 0000000..68cfedf
--- /dev/null
+++ b/src/main/java/login_zh_TW.mkd
@@ -0,0 +1,3 @@
+## 請登入
+
+請輸入密碼,以便登入此Gitblit版控伺服器
diff --git a/src/main/java/welcome_zh_TW.mkd b/src/main/java/welcome_zh_TW.mkd
new file mode 100644
index 0000000..eaaee65
--- /dev/null
+++ b/src/main/java/welcome_zh_TW.mkd
@@ -0,0 +1,3 @@
+## 歡迎來到Gitblit版本控管伺服器
+
+一個快速讓您能存放自己Git文件庫的解決方案  [Git](http://www.git-scm.com) 
diff --git a/src/main/resources/blink32.png b/src/main/resources/blink32.png
new file mode 100644
index 0000000..da59350
--- /dev/null
+++ b/src/main/resources/blink32.png
Binary files differ
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
index 748a319..0cc8fd0 100644
--- a/src/main/resources/gitblit.css
+++ b/src/main/resources/gitblit.css
@@ -38,6 +38,19 @@
     font-weight: bold;
 }
 
+.gray {
+	color: #888;
+}
+
+.octicon-centered {
+    text-align: center;
+    width: 16px;
+}
+
+tr:hover .octicon-centered {
+	color:#eee;
+}
+
 .label a.bugtraq {
 	font-weight: normal;
 	color: white;
@@ -237,14 +250,13 @@
 }
 
 .repositorynavbar {
-	background-color: #fbfbfb;
+	background-color: #f8f8f8;
 	border-bottom: 1px solid #ccc;
 	margin-bottom: 10px;
 }
 
 .repositorynavbar .title {
-	line-height: 32px;
-	padding: 5px 0px;
+	padding: 10px 0px;
 }
 
 .repositorynavbar .repository {
@@ -782,6 +794,10 @@
 
 td.ticket-list-state {
 	vertical-align: middle;
+}
+
+td.ticket-list-priority {
+	vertical-align: middle;
 }
 
 .ticket-list-details {
@@ -1334,19 +1350,6 @@
 	font-family: inherit;
 }
 
-div.diff.hunk_header {
-	-moz-border-bottom-colors: none;
-    -moz-border-image: none;
-    -moz-border-left-colors: none;
-    -moz-border-right-colors: none;
-    -moz-border-top-colors: none;
-    border-color: #FFE0FF;
-    border-style: dotted;
-    border-width: 1px 0 0;
-    margin-top: 2px;
-    font-family: inherit;
-}
-
 span.diff.hunk_info {
 	background-color: #FFEEFF;	
 	color: #990099;
@@ -1358,61 +1361,241 @@
 	font-family: inherit;
 }
 
-div.diff.add2 {
-	background-color: #DDFFDD;
-    font-family: inherit;
+.diff-cell {
+	margin: 0px;
+	padding: 0 2px;
+	border: 0;
+	border-left: 1px solid #bbb;
 }
 
-div.diff.remove2 {
+.add2 {
+    background-color: #DDFFDD;
+}
+
+.remove2 {
 	background-color: #FFDDDD;
-    font-family: inherit;
 }
 
-div.diff table {
+.context2 {
+	background-color: #FEFEFE;
+}
+
+.trailingws-add {
+    background-color: #99FF99;	
+}
+
+.trailingws-sub {
+    background-color: #FF9999;	
+}
+
+div.diff > table {
 	border-radius: 0;
 	border-right: 1px solid #bbb;
 	border-bottom: 1px solid #bbb;
 	width: 100%;
 }
 
-div.diff table th, div.diff table td {
-	margin: 0px;
-	padding: 0px;
-	font-family: monospace;
-	border: 0;
-}
-
-div.diff table th {
-	background-color: #f0f0f0;
+.diff-line {
+	background-color: #fbfbfb;
 	text-align: center;
 	color: #999;
-	padding-left: 5px;
-	padding-right: 5px;
-	width: 30px;
+	padding-left: 2px;
+	padding-right: 2px;
+	width: 3em; /* Font-size relative! */
+	min-width: 3em;
 }
 
-div.diff table th.header {
-	background-color: #D2C3AF;
-	border-right: 0px;
-	border-bottom: 1px solid #808080;
-	font-family: inherit;
-	font-size:0.9em;
-	color: black;
-	padding: 2px;
-	text-align: left;
+.diff-line:before {
+	content: attr(data-lineno);
 }
 
-div.diff table td.hunk_header {
+.diff-state {
+	background-color: #fbfbfb;
+	text-align: center;
+	color: #999;
+	padding-left: 2px;
+	padding-right: 2px;
+	width: 0.5em; /* Font-size relative! */
+}
+
+.diff-state-add:before {
+	color: green;
+	font-weight: bold;
+	content: '+';
+}
+
+.diff-state-sub:before {
+	color: red;
+	font-weight: bold;
+	content: '-';
+}
+
+.hunk_header {
 	background-color: #dAe2e5 !important;
+	border-left: 1px solid #bbb;
 	border-top: 1px solid #bac2c5;	
 	border-bottom: 1px solid #bac2c5;
 	color: #555;
 }
 
-div.diff table td {
-	border-left: 1px solid #bbb;
-	background-color: #fbfbfb;
+/* Image diffs. */
+
+/* Note: can't use gradients; IE < 10 doesn't support them. Use pre-created pngs with transparency instead. */
+
+/* Set on body during mouse tracking. */
+.no-select {
+	-webkit-touch-callout:none;
+	-webkit-user-select:none;
+	-khtml-user-select:none;
+	-moz-user-select:none;
+	-ms-user-select:none;
+	user-select:none;
 }
+
+div.imgdiff-container {
+	padding: 10px;
+	background: #EEE;
+}
+
+div.imgdiff {
+	margin: 10px 20px;
+	position:relative;
+	display: inline-block;
+	/* Checkerboard background to reveal transparency. */
+    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAK0lEQVQoz2O8e/cuAzagpKSEVZyJgUQwqoEYwPj//3+sEvfu3RsNJfppAACQZwizxs5QrAAAAABJRU5ErkJggg==);
+    background-repeat: repeat;
+	/* Same with CSS:
+	background-color: white;
+	background-image: linear-gradient(45deg, #DDD 25%, transparent 25%, transparent 75%, #DDD 75%, #DDD), linear-gradient(45deg, #DDD 25%, transparent 25%, transparent 75%, #DDD 75%, #DDD);
+	background-size:16px 16px;
+	background-position:0 0, 8px 8px;
+	*/
+}
+
+div.imgdiff-left {
+	position: absolute;
+	top: 0;
+	bottom: 0;
+	left: 0;
+	width: 0;
+	max-width: 100%;
+	overflow: hidden;
+}
+
+img.imgdiff {
+	user-select: none;
+	border: 1px solid #0F0;
+}
+img.imgdiff-old {
+	user-select: none;
+	border: 1px solid #F00;
+}
+
+.imgdiff-opa-container {
+	display: inline-block;
+	width: 200px;
+	height: 4px;
+	margin: 12px 35px 6px 35px;
+	padding: 0;
+	position: relative;
+	border: 1px solid #888;
+	background-color: #DDD;
+}
+
+.imgdiff-opa-container:before {
+	content: '';
+	position: absolute;
+	left: -20px;
+	top: -4px;
+	width : 12px;
+	height: 12px;
+	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAAA7klEQVQoz13STUrDMRAF8N+02fQDhCpqNyJIvUEXXfYCXtlFsZeQLpRCpYJ01RakxM38IRgIQyZv3rx5SchVaw1c4xm36OEb7/jqcJHgPuZYYopx3h2xwwrriPgtyTzHCx5xg1ESnvJ8lR1fS8pYJvgBEwyywwlD9LGotX6W1DxNpknutkPgnGSzkgOOEzRoogY8zPx9L7VFa0Ku//lAlLTumO1PjYxuhnMT9yV93uUMw2Q+NwU/OGCL95KPskrr+o3mruCAD7xhU1LjOpkX6caoGXib4HVEXKLWKiK67/GEGe6SYJ+SNxFxqbX2/gBxKkhxx1tQIAAAAABJRU5ErkJggg==);
+	/* With CSS: background-image: radial-gradient(6px at 50% 50%, rgba(255, 255, 255, 255) 50%, rgba(255, 255, 255, 0) 6px); */
+}
+
+.imgdiff-opa-container:after {
+	content: '';
+	position: absolute;
+	right: -20px;
+	top: -4px;
+	width : 12px;
+	height: 12px;
+	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAACXBIWXMAAAsTAAALEwEAmpwYAAABlklEQVQoz02Rr45TYRTE5/z7ynebYNBIFFuLXV2FQRIgWcUDkN3bBNnbR0CQJvsGGLCbIEFgSCrgGXCw7b3fOT2ILQm/ZJIRI2YyhBObzaZm5kVmvszMs8wkJtoR0ZZUr/vLyz/DMBCdwg+PcfzUvC3cHRGBTICZUYpBVb+KyPO+73/SZrOpEccv03hYHMbRp9bk6EEJQITTzFqttZjZ59ls9kwz88K9LQ7j6IfDXsdxgrcGEEFEqJRSAAQzn7fWXmtmvnJ3tNZkHCfc3t5iahOQgJWCBKCq1FqDiDzVzHwcEYgIcndMU8N+v0dmogNQzODufLcrzxiZhAQyEwBASGSedPL/wyDaMTNEJEUEpRi6rkPXdShWoKoQkWBmENGOmWhrxWBmrZSCe7ViPp9jPp+j1opSSpoZmxmY+YOS6rVmvqi1PgEQqkruzgAgImFmXGslVb1h5u2/4x65+/uIOG+tISLu+jLDzKCqN6r6pu/7bzIMA61Wq1/L5fIjgN8ict/MHphZmNl3VX1nJm+vrvof6/Xa/gJuQtCzTqj1dQAAAABJRU5ErkJggg==);
+	/* With CSS: background-image: radial-gradient(6px at 50% 50%, #888, #888 1px, transparent 6px); */
+}
+
+.imgdiff-opa-slider {
+	position:absolute;
+	top : 0;
+	left: -5px;
+	bottom: 0;
+	right: -5px;
+	text-align: left;
+}
+
+.imgdiff-opa-handle {
+	display: inline-block;
+	width: 10px;
+	height: 10px;
+	position: absolute;
+	top: -3px;
+	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+	/* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-ovr-slider {
+	display: inline-block;
+	margin: 0;
+	padding: 0;
+	position: relative;
+	text-align: left;
+}
+
+.imgdiff-ovr-handle {
+	display: inline-block;
+	width : 1px;
+	height: 100%;
+	top: 0px;
+	background-color: #444;
+	border-right: 1px solid #FFF;
+}
+
+.imgdiff-ovr-handle:before {
+	content: '';
+	position: absolute;
+	right: -4px;
+	bottom: -5px;
+	width : 10px;
+	height: 10px;
+	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+	/* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-ovr-handle:after {
+	content: '';
+	position: absolute;
+	right: -4px;
+	top: -5px;
+	width : 10px;
+	height: 10px;
+	background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAACXBIWXMAAAsTAAALEwEAmpwYAAABMklEQVQY022OsU7CUBRA723fpTS0NcVBJuOoQUed0MSJhOiP0IHK6g84ShoM/2HE1TjZxeCA0UF3RW2tLQ73mesgLuiZT3IOwoww7FSLYrrDzHUAMInUbblsX0RR9NJsNgEBAIIg2EjT9DDP8y2ttSsiqEhlTsW58TzvaDAYxBiGB9XJ5PkkSZJdZvZFRAEAIKImUm+et3BVqy11VFHkjTzPN2cS/a6ICDHrxWlR1N+zjz2DmddnOQVziIjJWnuaedUAAENEEP4iPweIImIYRDQmogwR9T+SVkplpVLpzrBt+9KpVEaK6BUReU6a2LY9dl33zIzj+LOx3XgUkRUQsADwSylzalnWk+M4I9/3o36/f42t/RYMT4fQ7XaX0zRpMes1ADAU0b3nuee9495DO2ib37s7ktpA7ddzAAAAAElFTkSuQmCC);
+	/* With CSS: background-image: radial-gradient(5px at 50% 50%, #444, #888, transparent 5px); */
+}
+
+.imgdiff-link {
+	margin: 0px 4px;
+	text-decoration: none;
+	border: none;
+}
+
+.imgdiff-link > img {
+	border: 1px solid transparent; /* Avoid jumping when we change the border */
+	width: 20px;
+	height: 20px;
+	margin-bottom: 10px;
+}
+
+/* End image diffs */
 
 td.changeType {
 	width: 15px;
@@ -1546,12 +1729,22 @@
   }	
 }
 
+td.sha256 {
+	max-width: 20em;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+
 table.comments td {
 	padding: 4px;
 	line-height: 17px;
 }
 
-table.repositories {	
+table.projectlist {
+	margin-top: 10px;
+}
+
+table.repositories {
 	border:1px solid #ddd;
 	border-spacing: 0px;
 	width: 100%;
@@ -1735,7 +1928,7 @@
 	white-space: nowrap;
 }
 
-span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1, td.sha1 {
+span.sha1, span.sha1 a, span.sha1 a span, .commit_message, span.shortsha1, td.sha1, td.sha256 {
 	font-family: consolas, monospace;
 	font-size: 13px;
 }
@@ -2079,4 +2272,85 @@
     background-color: #fff;
     border-color: #ece7e2;
     color: #815b3a;
-}
\ No newline at end of file
+}
+.severity-catastrophic {
+	color:#D51900;
+}
+.severity-catastrophic:after {
+	font-family: Helvetica,arial,freesans,clean,sans-serif ;
+	content: "●●●●●";
+	font-weight:900;
+	font-size:.45em;	
+	font-variant:small-caps;
+	display:flex;
+	white-space: pre;
+}
+.severity-critical {
+	color:#D55900;
+}
+.severity-critical:after {
+	font-family: Helvetica,arial,freesans,clean,sans-serif ;
+	content: " ●●●●";
+	font-weight:900;
+	font-size:.45em;	
+	font-variant:small-caps;
+	display:flex;
+	white-space: pre;
+}
+.severity-serious {
+	color:#E69F00;
+}
+.severity-serious:after {
+	font-family: Helvetica,arial,freesans,clean,sans-serif ;
+	content: "  ●●●";
+	font-weight:900;
+	font-size:.45em;	
+	font-variant:small-caps;
+	display:flex;
+	white-space: pre;
+}
+.severity-minor {
+	color:#009E73;
+}
+.severity-minor:after {
+	font-family: Helvetica,arial,freesans,clean,sans-serif ;
+	content: "   ●●";
+	font-weight:900;
+	font-size:.45em;	
+	font-variant:small-caps;
+	display:flex;
+	white-space: pre;
+}
+.severity-negligible {
+	color:#0072B2;
+}
+.severity-negligible:after {
+	font-family: Helvetica,arial,freesans,clean,sans-serif ;
+	content: "    ●";
+	font-weight:900;
+	font-size:.45em;	
+	font-variant:small-caps;
+	display:flex;
+	white-space: pre;
+}
+.severity-unrated {
+}
+.priority-urgent {
+	color:#D51900;
+}
+.priority-high {
+	color:#D55900;
+}
+.priority-normal {
+}
+.priority-low {
+	color:#0072B2;
+}
+
+.file-positive {
+	color:#009E73;
+}
+
+.file-negative {
+	color:#D51900;
+}
diff --git a/src/main/resources/octicons/octicons-local.ttf b/src/main/resources/octicons/octicons-local.ttf
new file mode 100644
index 0000000..03d78cc
--- /dev/null
+++ b/src/main/resources/octicons/octicons-local.ttf
Binary files differ
diff --git a/src/main/resources/octicons/octicons.css b/src/main/resources/octicons/octicons.css
new file mode 100644
index 0000000..a5dcd15
--- /dev/null
+++ b/src/main/resources/octicons/octicons.css
@@ -0,0 +1,235 @@
+@font-face {
+  font-family: 'octicons';
+  src: url('octicons.eot?#iefix') format('embedded-opentype'),
+       url('octicons.woff') format('woff'),
+       url('octicons.ttf') format('truetype'),
+       url('octicons.svg#octicons') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+
+/*
+
+.octicon is optimized for 16px.
+.mega-octicon is optimized for 32px but can be used larger.
+
+*/
+.octicon, .mega-octicon {
+  font: normal normal normal 16px/1 octicons;
+  display: inline-block;
+  text-decoration: none;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-alignment-align:before { content: '\f08a'} /*  */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */
+.octicon-alignment-unalign:before { content: '\f08b'} /*  */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /*  */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
+.octicon-arrow-small-right:before { content: '\f071'} /*  */
+.octicon-arrow-small-up:before { content: '\f09f'} /*  */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-beer:before { content: '\f069'} /*  */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /*  */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /*  */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /*  */
+.octicon-calendar:before { content: '\f068'} /*  */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /*  */
+.octicon-chevron-down:before { content: '\f0a3'} /*  */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /*  */
+.octicon-chevron-up:before { content: '\f0a2'} /*  */
+.octicon-circle-slash:before { content: '\f084'} /*  */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /*  */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /*  */
+.octicon-color-mode:before { content: '\f065'} /*  */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /*  */
+.octicon-credit-card:before { content: '\f045'} /*  */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /*  */
+.octicon-database:before { content: '\f096'} /*  */
+.octicon-device-camera:before { content: '\f056'} /*  */
+.octicon-device-camera-video:before { content: '\f057'} /*  */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /*  */
+.octicon-diff-added:before { content: '\f06b'} /*  */
+.octicon-diff-ignored:before { content: '\f099'} /*  */
+.octicon-diff-modified:before { content: '\f06d'} /*  */
+.octicon-diff-removed:before { content: '\f06c'} /*  */
+.octicon-diff-renamed:before { content: '\f06e'} /*  */
+.octicon-ellipsis:before { content: '\f09a'} /*  */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /*  */
+.octicon-file-binary:before { content: '\f094'} /*  */
+.octicon-file-code:before { content: '\f010'} /*  */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /*  */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /*  */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /*  */
+.octicon-home:before { content: '\f08d'} /*  */
+.octicon-horizontal-rule:before { content: '\f070'} /*  */
+.octicon-hourglass:before { content: '\f09e'} /*  */
+.octicon-hubot:before { content: '\f09d'} /*  */
+.octicon-inbox:before { content: '\f0cf'} /*  */
+.octicon-info:before { content: '\f059'} /*  */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-jump-down:before { content: '\f072'} /*  */
+.octicon-jump-left:before { content: '\f0a5'} /*  */
+.octicon-jump-right:before { content: '\f0a6'} /*  */
+.octicon-jump-up:before { content: '\f073'} /*  */
+.octicon-key:before { content: '\f049'} /*  */
+.octicon-keyboard:before { content: '\f00d'} /*  */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /*  */
+.octicon-link-external:before { content: '\f07f'} /*  */
+.octicon-list-ordered:before { content: '\f062'} /*  */
+.octicon-list-unordered:before { content: '\f061'} /*  */
+.octicon-location:before { content: '\f060'} /*  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /*  */
+.octicon-logo-github:before { content: '\f092'} /*  */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /*  */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /*  */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-microscope:before { content: '\f089'} /*  */
+.octicon-milestone:before { content: '\f075'} /*  */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-move-down:before { content: '\f0a8'} /*  */
+.octicon-move-left:before { content: '\f074'} /*  */
+.octicon-move-right:before { content: '\f0a9'} /*  */
+.octicon-move-up:before { content: '\f0a7'} /*  */
+.octicon-mute:before { content: '\f080'} /*  */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /*  */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /*  */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */
+.octicon-playback-pause:before { content: '\f0bb'} /*  */
+.octicon-playback-play:before { content: '\f0bf'} /*  */
+.octicon-playback-rewind:before { content: '\f0bc'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /*  */
+.octicon-podium:before { content: '\f0af'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /*  */
+.octicon-primitive-square:before { content: '\f053'} /*  */
+.octicon-pulse:before { content: '\f085'} /*  */
+.octicon-puzzle:before { content: '\f0c0'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /*  */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /*  */
+.octicon-repo-clone:before { content: '\f04c'} /*  */
+.octicon-repo-force-push:before { content: '\f04a'} /*  */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /*  */
+.octicon-screen-full:before { content: '\f066'} /*  */
+.octicon-screen-normal:before { content: '\f067'} /*  */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /*  */
+.octicon-settings:before { content: '\f07c'} /*  */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-split:before { content: '\f0c6'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-steps:before { content: '\f0c7'} /*  */
+.octicon-stop:before { content: '\f08f'} /*  */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /*  */
+.octicon-triangle-down:before { content: '\f05b'} /*  */
+.octicon-triangle-left:before { content: '\f044'} /*  */
+.octicon-triangle-right:before { content: '\f05a'} /*  */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /*  */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /*  */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/src/main/resources/octicons/octicons.eot b/src/main/resources/octicons/octicons.eot
new file mode 100644
index 0000000..22881a8
--- /dev/null
+++ b/src/main/resources/octicons/octicons.eot
Binary files differ
diff --git a/src/main/resources/octicons/octicons.less b/src/main/resources/octicons/octicons.less
new file mode 100644
index 0000000..b054eb8
--- /dev/null
+++ b/src/main/resources/octicons/octicons.less
@@ -0,0 +1,233 @@
+@octicons-font-path: ".";
+@octicons-version:   "a594b5fd4cae0b2afd156bca8dad8d27ac3d7594";
+
+@font-face {
+  font-family: 'octicons';
+  src: ~"url('@{octicons-font-path}/octicons.eot?#iefix&v=@{octicons-version}') format('embedded-opentype')",
+       ~"url('@{octicons-font-path}/octicons.woff?v=@{octicons-version}') format('woff')",
+       ~"url('@{octicons-font-path}/octicons.ttf?v=@{octicons-version}') format('truetype')",
+       ~"url('@{octicons-font-path}/octicons.svg?v=@{octicons-version}#octicons') format('svg')";
+  font-weight: normal;
+  font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+  font: normal normal normal 16px/1 octicons;
+  display: inline-block;
+  text-decoration: none;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-alignment-align:before { content: '\f08a'} /*  */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */
+.octicon-alignment-unalign:before { content: '\f08b'} /*  */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /*  */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
+.octicon-arrow-small-right:before { content: '\f071'} /*  */
+.octicon-arrow-small-up:before { content: '\f09f'} /*  */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-beer:before { content: '\f069'} /*  */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /*  */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /*  */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /*  */
+.octicon-calendar:before { content: '\f068'} /*  */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /*  */
+.octicon-chevron-down:before { content: '\f0a3'} /*  */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /*  */
+.octicon-chevron-up:before { content: '\f0a2'} /*  */
+.octicon-circle-slash:before { content: '\f084'} /*  */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /*  */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /*  */
+.octicon-color-mode:before { content: '\f065'} /*  */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /*  */
+.octicon-credit-card:before { content: '\f045'} /*  */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /*  */
+.octicon-database:before { content: '\f096'} /*  */
+.octicon-device-camera:before { content: '\f056'} /*  */
+.octicon-device-camera-video:before { content: '\f057'} /*  */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /*  */
+.octicon-diff-added:before { content: '\f06b'} /*  */
+.octicon-diff-ignored:before { content: '\f099'} /*  */
+.octicon-diff-modified:before { content: '\f06d'} /*  */
+.octicon-diff-removed:before { content: '\f06c'} /*  */
+.octicon-diff-renamed:before { content: '\f06e'} /*  */
+.octicon-ellipsis:before { content: '\f09a'} /*  */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /*  */
+.octicon-file-binary:before { content: '\f094'} /*  */
+.octicon-file-code:before { content: '\f010'} /*  */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /*  */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /*  */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /*  */
+.octicon-home:before { content: '\f08d'} /*  */
+.octicon-horizontal-rule:before { content: '\f070'} /*  */
+.octicon-hourglass:before { content: '\f09e'} /*  */
+.octicon-hubot:before { content: '\f09d'} /*  */
+.octicon-inbox:before { content: '\f0cf'} /*  */
+.octicon-info:before { content: '\f059'} /*  */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-jump-down:before { content: '\f072'} /*  */
+.octicon-jump-left:before { content: '\f0a5'} /*  */
+.octicon-jump-right:before { content: '\f0a6'} /*  */
+.octicon-jump-up:before { content: '\f073'} /*  */
+.octicon-key:before { content: '\f049'} /*  */
+.octicon-keyboard:before { content: '\f00d'} /*  */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /*  */
+.octicon-link-external:before { content: '\f07f'} /*  */
+.octicon-list-ordered:before { content: '\f062'} /*  */
+.octicon-list-unordered:before { content: '\f061'} /*  */
+.octicon-location:before { content: '\f060'} /*  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /*  */
+.octicon-logo-github:before { content: '\f092'} /*  */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /*  */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /*  */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-microscope:before { content: '\f089'} /*  */
+.octicon-milestone:before { content: '\f075'} /*  */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-move-down:before { content: '\f0a8'} /*  */
+.octicon-move-left:before { content: '\f074'} /*  */
+.octicon-move-right:before { content: '\f0a9'} /*  */
+.octicon-move-up:before { content: '\f0a7'} /*  */
+.octicon-mute:before { content: '\f080'} /*  */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /*  */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /*  */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */
+.octicon-playback-pause:before { content: '\f0bb'} /*  */
+.octicon-playback-play:before { content: '\f0bf'} /*  */
+.octicon-playback-rewind:before { content: '\f0bc'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /*  */
+.octicon-podium:before { content: '\f0af'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /*  */
+.octicon-primitive-square:before { content: '\f053'} /*  */
+.octicon-pulse:before { content: '\f085'} /*  */
+.octicon-puzzle:before { content: '\f0c0'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /*  */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /*  */
+.octicon-repo-clone:before { content: '\f04c'} /*  */
+.octicon-repo-force-push:before { content: '\f04a'} /*  */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /*  */
+.octicon-screen-full:before { content: '\f066'} /*  */
+.octicon-screen-normal:before { content: '\f067'} /*  */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /*  */
+.octicon-settings:before { content: '\f07c'} /*  */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-split:before { content: '\f0c6'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-steps:before { content: '\f0c7'} /*  */
+.octicon-stop:before { content: '\f08f'} /*  */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /*  */
+.octicon-triangle-down:before { content: '\f05b'} /*  */
+.octicon-triangle-left:before { content: '\f044'} /*  */
+.octicon-triangle-right:before { content: '\f05a'} /*  */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /*  */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /*  */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/src/main/resources/octicons/octicons.svg b/src/main/resources/octicons/octicons.svg
new file mode 100644
index 0000000..ea3e0f1
--- /dev/null
+++ b/src/main/resources/octicons/octicons.svg
@@ -0,0 +1,198 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg xmlns="http://www.w3.org/2000/svg">
+<metadata>
+(c) 2012-2014 GitHub
+
+When using the GitHub logos, be sure to follow the GitHub logo guidelines (https://github.com/logos)
+
+Font License: SIL OFL 1.1 (http://scripts.sil.org/OFL)
+Applies to all font files
+
+Code License: MIT (http://choosealicense.com/licenses/mit/)
+Applies to all other files
+</metadata>
+<defs>
+<font id="octicons" horiz-adv-x="1024" >
+<font-face font-family="octicons" font-weight="400" font-stretch="normal" units-per-em="1024" ascent="832" descent="-192" />
+<missing-glyph d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" />
+<glyph glyph-name="alert" unicode="&#xf02d;" d="M1005.854 31.753000000000043l-438.286 767C556.173 818.694 534.967 831 512 831s-44.173-12.306-55.567-32.247l-438.286-767c-11.319-19.809-11.238-44.144 0.213-63.876C29.811-51.85500000000002 50.899-64 73.714-64h876.572c22.814 0 43.903 12.145 55.354 31.877S1017.173 11.94399999999996 1005.854 31.753000000000043zM576 64H448V192h128V64zM576 256H448V512h128V256z" horiz-adv-x="1024" />
+<glyph glyph-name="alignment-align" unicode="&#xf08a;" d="M192 768C85.938 768 0 682.062 0 576s85.938-192 192-192c106.062 0 192 85.938 192 192S298.062 768 192 768zM672 224l160 160H384v-448l160 160 288-288 128 128L672 224z" horiz-adv-x="960" />
+<glyph glyph-name="alignment-aligned-to" unicode="&#xf08e;" d="M384 256l128 128 288-288 160 160v-448H512l160 160L384 256zM192 384C85.938 384 0 469.938 0 576S85.938 768 192 768c106.062 0 192-85.938 192-192S298.062 384 192 384z" horiz-adv-x="960" />
+<glyph glyph-name="alignment-unalign" unicode="&#xf08b;" d="M512 640L384 512 128 768 0 640l256-256L128 256l64-64 384 384L512 640zM640 256l128 128-64 64L320 64l64-64 128 128 256-256 128 128L640 256z" horiz-adv-x="896" />
+<glyph glyph-name="arrow-down" unicode="&#xf03f;" d="M448 384V640H192v-256H0l320-384 320 384H448z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-left" unicode="&#xf040;" d="M384 448V640L0 320l384-320V192h256V448H384z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-right" unicode="&#xf03e;" d="M640 320L256 640v-192H0v-256h256v-192L640 320z" horiz-adv-x="640" />
+<glyph glyph-name="arrow-small-down" unicode="&#xf0a0;" d="M256 384V512H128v-128H0l192-256 192 256H256z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-left" unicode="&#xf0a1;" d="M256 384V512L0 320l256-192V256h128V384H256z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-right" unicode="&#xf071;" d="M384 320L128 512v-128H0v-128h128v-128L384 320z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-small-up" unicode="&#xf09f;" d="M192 512L0 256h128v-128h128V256h128L192 512z" horiz-adv-x="384" />
+<glyph glyph-name="arrow-up" unicode="&#xf03d;" d="M320 640L0 256h192v-256h256V256h192L320 640z" horiz-adv-x="640" />
+<glyph glyph-name="beer" unicode="&#xf069;" d="M896 576c-31 0-192 0-192 0v128c0 71-158 128-352 128s-352-57-352-128v-768c0-71 158-128 352-128s352 57 352 128v128s160 0 192 0 64 30 64 64 0 350 0 384-29 64-64 64z m-704-576h-64v512h64v-512z m192-64h-64v512h64v-512z m192 64h-64v512h64v-512z m-224 640c-124 0-224 29-224 64s100 64 224 64 224-29 224-64-100-64-224-64z m480-448h-128v256h128v-256z" horiz-adv-x="1024" />
+<glyph glyph-name="book" unicode="&#xf007;" d="M768 256h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-55 416c-167 0-209-32-233-56-24 24-66 56-233 56s-247-46-247-78v-586c29 16 119 48 214 56 115 9 234-9 234-32 0-16 8-31 31-32 0 0 0 0 1 0 0 0 0 0 1 0 23 1 31 16 31 32 0 23 119 41 234 32 94-7 185-40 214-56v586c0 32-80 78-247 78z m-265-572c-30 16-103 28-192 28s-170-12-192-27c0 0 0 411 0 443s64 59 192 59 192-27 192-59 0-444 0-444z m448 1c-22 15-103 27-192 27s-162-12-192-28c0 0 0 412 0 444s64 59 192 59 192-27 192-59 0-443 0-443z m-128 283h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m0 128h-128c-34 0-64-32-64-64h256c0 34-32 64-64 64z m-448-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0-128h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z m0 256h-128c-32 0-64-30-64-64h256c0 32-30 64-64 64z" horiz-adv-x="1024" />
+<glyph glyph-name="bookmark" unicode="&#xf07b;" d="M0 704v-768l192 128 192-128V704H0zM316.25 507.25l-71.875-51.938 27.188-83.406c2.75-8.375-0.688-11.062-7.562-6.594l-72 52.094-72-52.031c-6.844-4.469-10.312-1.781-7.562 6.594l27.219 83.406L67.783 507.25c-6.469 5.125-5 9.219 3.906 9.219l88 0.125 27.125 83.094c2.812 8.812 7.562 8.812 10.375 0l27.188-83.094 87.938-0.125C321.25 516.469 322.688 512.375 316.25 507.25z" horiz-adv-x="384" />
+<glyph glyph-name="briefcase" unicode="&#xf0d3;" d="M896 640H640v66c0 34.2-27.8 62-62 62H446c-34.2 0-62-27.8-62-62v-66H128c-35.3 0-64-28.7-64-64v-512c0-35.3 28.7-64 64-64h768c35.3 0 64 28.7 64 64V576C960 611.3 931.3 640 896 640zM448 688c0 8.8 7.2 16 16 16h96c8.8 0 16-7.2 16-16v-48H448V688zM896 320H576v-64H448v64H128V576h64v-192h640V576h64V320z" horiz-adv-x="1024" />
+<glyph glyph-name="broadcast" unicode="&#xf048;" d="M448 640c142 0 256-115 256-256 0-69-28-132-72-178l-16-93c91 56 152 156 152 271 0 177-143 320-320 320s-320-143-320-320c0-115 61-215 152-271l-16 93c-45 46-72 109-72 178 0 142 114 256 256 256z m-64-320c-36 0-64-29-64-64v-128c0-36 30-64 64-64v-256h128v256c34 0 64 28 64 64v128c0 35-28 64-64 64s-64 0-64 0-28 0-64 0z m192 128c0 71-57 128-128 128s-128-57-128-128 57-128 128-128 128 57 128 128z m-128 384c-247 0-448-201-448-448 0-197 128-363 305-423l-12 72c-135 60-229 194-229 351 0 212 172 384 384 384s384-172 384-384c0-157-94-291-229-351l-12-72c177 60 305 225 305 423 0 247-201 448-448 448z" horiz-adv-x="896" />
+<glyph glyph-name="browser" unicode="&#xf0c5;" d="M320 640h64v-64h-64V640zM192 640h64v-64h-64V640zM64 640h64v-64H64V640zM832 0H64V512h768V0zM832 576H448v64h384V576zM896 640c0 35.35-28.65 64-64 64H64c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V640z" horiz-adv-x="896" />
+<glyph glyph-name="bug" unicode="&#xf091;" d="M243.621 675.469C190.747 618.688 205.34 528 205.34 528s53.968-64 160-64c106.031 0 160.031 64 160.031 64s14.375 89.469-37.375 146.312c32.375 18.031 51.438 44.094 43.562 61.812-8.938 19.969-48.375 21.75-88.25 3.969-14.812-6.594-27.438-14.969-37.25-23.875-12.438 2.25-25.625 3.781-40.72 3.781-14.061 0-26.561-1.344-38.344-3.25-9.656 8.75-22.062 16.875-36.531 23.344-39.875 17.719-79.375 15.938-88.25-3.969C194.465 718.781 212.497 693.438 243.621 675.469zM644.746 262.25c-8.25 1.75-16.125 2.75-23.75 3.5 0 2.125 0.375 4.125 0.375 6.312 0 33.594-4.75 65.654-12.438 96.125 16.438-1.406 37.375 2.375 58.562 11.779 39.875 17.781 65 48.375 56.125 68.219-8.875 19.969-48.375 21.75-88.25 3.969-18.625-8.312-33.812-19.469-44-30.906-7.75 18.25-16.5 35.781-26.812 51.719-30.188-25.156-87.312-62.719-167.062-71.062v-321.781c0 0-0.25-32-32.031-32-31.75 0-32 32-32 32V401.781c-79.811 8.344-136.968 45.969-167.093 71.062-9.875-15.312-18.375-32-25.938-49.344-10.281 10.625-24.625 20.844-41.969 28.594-39.875 17.719-79.375 15.938-88.25-3.969-8.906-19.906 16.25-50.438 56.125-68.219 19.844-8.846 39.531-12.812 55.469-12.096-7.656-30.404-12.469-62.344-12.469-95.812 0-2.188 0.375-4.25 0.438-6.5-6.719-0.75-13.688-1.75-20.781-3.25-51.969-10.75-91.781-37.625-88.844-59.812 2.938-22.312 47.5-31.5 99.594-20.688 6.781 1.375 13.438 3.125 19.781 5.062C128.684 146 143.34 108.125 163.622 75.5c-12.031-6.062-24.531-15-36.031-26.625C95.715 17 82.779-21.75 98.715-37.68799999999999c15.938-15.937 54.656-3 86.531 28.812 9.344 9.375 16.844 19.25 22.656 29C251.434-22.5 305.965-48 365.465-48c60.343 0 115.781 26.25 159.531 69.938 5.875-10.312 13.75-20.812 23.625-30.688 31.812-31.875 70.625-44.812 86.562-28.875s3 54.625-28.875 86.5c-12.312 12.375-25.688 21.75-38.438 27.938 20.125 32.5 34.625 70.375 43.688 111.062 7.188-2.25 14.688-4.375 22.562-6.062 52.061-10.812 96.625-1.562 99.625 20.688C736.558 224.625 696.746 251.5 644.746 262.25z" horiz-adv-x="733.886" />
+<glyph glyph-name="calendar" unicode="&#xf068;" d="M704 320h-64v-128h64V320zM576 320h-64v-128h64V320zM704 512h-64v-128h64V512zM832 320h-64v-128h64V320zM576 128h-64v-128h64V128zM768 832h-64v-128h64V832zM256 832h-64v-128h64V832zM832 512h-64v-128h64V512zM576 512h-64v-128h64V512zM320 128h-64v-128h64V128zM192 320h-64v-128h64V320zM320 320h-64v-128h64V320zM832 768v-128H640V768H320v-128H128V768H0v-896h960V768H832zM896-64H64V576h832V-64zM192 128h-64v-128h64V128zM448 512h-64v-128h64V512zM448 128h-64v-128h64V128zM320 512h-64v-128h64V512zM448 320h-64v-128h64V320zM704 128h-64v-128h64V128z" horiz-adv-x="1024" />
+<glyph glyph-name="check" unicode="&#xf03a;" d="M640 640L256 256 128 384 0 256l256-256 512 512L640 640z" horiz-adv-x="768" />
+<glyph glyph-name="checklist" unicode="&#xf076;" d="M760.688 315.78099999999995l-49.812 49.656c-6.438 6.529-16.938 6.594-23.375 0L582.5 260.5 462.375 140.125l-93.031 93.125c-6.531 6.562-17.031 6.562-23.5 0l-49.719-49.688c-6.531-6.562-6.531-17.062 0-23.562l104.781-104.875 17.969-17.875 31.688-31.812c6.562-6.562 17.188-6.562 23.562 0l49.625 49.688L760.625 292.22C767.25 298.688 767.25 309.188 760.688 315.78099999999995zM228.469 251.188L278.156 301c42.469 42.375 116.344 42.438 158.781-0.062l25.312-25.312L576 384V704H0v-704h320l-91.531 92.125C184.688 136.062 184.688 207.375 228.469 251.188zM192 640h320v-64H192V640zM192 512h320v-64H192V512zM128 320H64v64h64V320zM128 448H64v64h64V448zM128 576H64v64h64V576zM192 384h64v-64h-64V384z" horiz-adv-x="765.602" />
+<glyph glyph-name="chevron-down" unicode="&#xf0a3;" d="M512 512L320 320 128 512 0 384l320-320 320 320L512 512z" horiz-adv-x="640" />
+<glyph glyph-name="chevron-left" unicode="&#xf0a4;" d="M448 512L320 640 0 320l320-320 128 128L256 320 448 512z" horiz-adv-x="448" />
+<glyph glyph-name="chevron-right" unicode="&#xf078;" d="M128 640L0 512l192-192L0 128l128-128 320 320L128 640z" horiz-adv-x="448" />
+<glyph glyph-name="chevron-up" unicode="&#xf0a2;" d="M320 576L0 256l128-128 192 192 192-192 128 128L320 576z" horiz-adv-x="640" />
+<glyph glyph-name="circle-slash" unicode="&#xf084;" d="M320 640C143.219 640 0 496.781 0 320c0-176.75 143.219-320 320-320 176.75 0 320 143.25 320 320C640 496.781 496.75 640 320 640zM320 512c27.656 0 53.688-6.094 77.438-16.562L144.562 242.562C134.094 266.312 128 292.34400000000005 128 320 128 426 213.938 512 320 512zM320 128c-28.031 0-54.531 6.375-78.594 17.125l253.906 252.5C505.875 373.812 512 347.719 512 320 512 213.938 426.062 128 320 128z" horiz-adv-x="640" />
+<glyph glyph-name="circuit-board" unicode="&#xf0d6;" d="M320 576c35.346 0 64-28.654 64-64 0-35.346-28.654-64-64-64s-64 28.654-64 64C256 547.346 284.654 576 320 576zM960 64c0-106.039-85.961-192-192-192H320l192 192h81.128c22.132-38.258 63.494-64 110.872-64 70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.377 0-88.74-25.742-110.872-64H448L156.044-99.95600000000002C100.845-66.23199999999997 64-5.419999999999959 64 64V576c0 106.039 85.961 192 192 192v-145.128C217.742 600.74 192 559.377 192 512c0-70.692 57.308-128 128-128 47.276 0 88.56 25.633 110.727 63.756l162.416 0.219C615.279 409.731 656.633 384 704 384c70.692 0 128 57.308 128 128s-57.308 128-128 128c-47.388 0-88.758-25.753-110.887-64.025l-162.097-0.219c-11.246 19.54-27.503 35.828-47.016 47.116V768h384c106.039 0 192-85.961 192-192V64zM640 128c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 92.654 640 128zM640 512c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64S640 476.654 640 512z" horiz-adv-x="1024" />
+<glyph glyph-name="clippy" unicode="&#xf035;" d="M704-64h-640v576h640v-192h64v320c0 35-29 64-64 64h-192c0 71-57 128-128 128s-128-57-128-128h-192c-35 0-64-29-64-64v-704c0-35 29-64 64-64h640c35 0 64 29 64 64v128h-64v-128z m-512 704c29 0 29 0 64 0s64 29 64 64 29 64 64 64 64-29 64-64 32-64 64-64 33 0 64 0 64-29 64-64h-512c0 39 28 64 64 64z m-64-512h128v64h-128v-64z m448 128v128l-256-192 256-192v128h320v128h-320z m-448-256h192v64h-192v-64z m320 448h-320v-64h320v64z m-192-128h-128v-64h128v64z" horiz-adv-x="896" />
+<glyph glyph-name="clock" unicode="&#xf046;" d="M384 256h256l64 64-64 64H512V576l-64 64-64-64V256zM448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.25 0-320 143.75-320 320 0 175.938 144.188 319.5 320 320 175.812-0.5 320-144.062 320-320C768 143.75 624.25 0 448 0z" horiz-adv-x="896" />
+<glyph glyph-name="cloud-download" unicode="&#xf00b;" d="M832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.75 644.125 704 512 704c-132.156 0-245.562-80.25-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426.062 0 320s85.938-192 192-192c20.531 0 39.875 4.25 58.375 10.375C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.875C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.094C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.625 156.938 64.438C830 128.375 830.875 128 832 128c106.062 0 192 85.938 192 192S938.062 512 832 512zM576 320H448v-320H320l192-192 192 192H576V320z" horiz-adv-x="1024" />
+<glyph glyph-name="cloud-upload" unicode="&#xf00c;" d="M512 448L320 256h128v-320h128V256h128L512 448zM832 512c-8.75 0-17.125-1.406-25.625-2.562C757.625 623.812 644.125 704 512 704c-132.156 0-245.562-80.188-294.406-194.562C209.156 510.594 200.781 512 192 512 85.938 512 0 426 0 320c0-106.062 85.938-192 192-192 20.531 0 39.875 4.25 58.375 10.438C284.469 100.625 331.312 75.25 384 67.5v65.25c-49.844 10.375-91.594 42.812-112.625 87.75C249.531 203 222.219 192 192 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 25.281 0 48.625-7.562 68.406-20.156C281.344 548.219 385.594 640 512 640c126.5 0 229.75-92.219 250.5-212.75 20 13 43.875 20.75 69.5 20.75 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128-10.25 0-20 1.5-29.625 3.75C773.438 154.875 725.938 128 672 128c-11.062 0-21.625 1.625-32 4v-64.938c10.438-1.688 21.062-3.062 32-3.062 61.188 0 116.5 24.688 157 64.438 1 0 1.875-0.438 3-0.438 106.062 0 192 85.938 192 192C1024 426 938.062 512 832 512z" horiz-adv-x="1024" />
+<glyph glyph-name="code" unicode="&#xf05f;" d="M608 640l-96-96 224-224L512 96l96-96 288 320L608 640zM288 640L0 320l288-320 96 96L160 320l224 224L288 640z" horiz-adv-x="896" />
+<glyph glyph-name="color-mode" unicode="&#xf065;" d="M0 704v-768h768V704H0zM64 0V640h640L64 0z" horiz-adv-x="768" />
+<glyph glyph-name="comment" unicode="&#xf02b;" d="M768 704H128C66 704 0 640 0 576v-384c0-128 128-128 128-128h64v-256l256 256c0 0 258 0 320 0s128 68 128 128V576C896 638 832 704 768 704z" horiz-adv-x="896" />
+<glyph glyph-name="comment-discussion" unicode="&#xf04f;" d="M256 320c0 64 0 192 0 192s-160 0-192 0-64-32-64-64 0-288 0-320 32-64 64-64 64 0 64 0v-192l194 192s162 0 192 0 62 32 62 64 0 64 0 64-128 0-192 0-128 64-128 128z m576 384c-32 0-416 0-448 0s-64-32-64-64 0-288 0-320 32-64 64-64 190 0 190 0l194-192v192s32 0 64 0 64 32 64 64 0 288 0 320-32 64-64 64z" horiz-adv-x="896" />
+<glyph glyph-name="credit-card" unicode="&#xf045;" d="M128 128h128v64h-128v-64z m192 0h128v64h-128v-64z m64 192h-256v-64h256v64z m-128 64h64l128 128h-64l-128-128z m192-128h192v64h-192v-64z m512 384c-32 0-864 0-896 0s-64-32-64-64 0-480 0-512 32-64 64-64 864 0 896 0 64 32 64 64 0 480 0 512-32 64-64 64z m0-256v-288s0-32-32-32h-832c-32 0-32 32-32 32v288h64l128 128h-192v32s0 32 32 32h832c32 0 32-32 32-32v-32h-384l-128-128h512z" horiz-adv-x="1024" />
+<glyph glyph-name="dash" unicode="&#xf0ca;" d="M0 384v-128h512V384H0z" horiz-adv-x="512" />
+<glyph glyph-name="dashboard" unicode="&#xf07d;" d="M416 367.5c-61.562 0-111.5-49.938-111.5-111.5S354.438 144.5 416 144.5 527.5 194.438 527.5 256c0 8.5-1.125 16.75-3 24.688C606.125 375.625 732.5 523.656 800 608c23.125 28.875-2.312 56.188-32 32-85.188-69.375-232.312-194.688-326.906-275.594C433.031 366.281 424.625 367.5 416 367.5zM447.875 576.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S447.875 558.469 447.875 576.125zM639.875 320.125c0-17.656 14.375-32 32-32s32 14.344 32 32-14.375 32-32 32S639.875 337.781 639.875 320.125zM287.875 576.125c-17.656 0-32-14.344-32-32s14.344-32 32-32 32 14.344 32 32S305.531 576.125 287.875 576.125zM223.875 448.125c0 17.656-14.344 32-32 32s-32-14.344-32-32 14.344-32 32-32S223.875 430.469 223.875 448.125zM127.875 320.125c0-17.656 14.344-32 32-32s32 14.344 32 32-14.344 32-32 32S127.875 337.781 127.875 320.125zM575.875 544.125c0 17.656-14.375 32-32 32s-32-14.344-32-32 14.375-32 32-32S575.875 526.469 575.875 544.125zM792.875 495.312l-68.75-89.938C731.625 378.188 736 349.625 736 320c0-176.75-143.312-320-320-320S96 143.25 96 320c0 176.688 143.312 320 320 320 65.875 0 127-19.969 177.875-54.094l79.25 60.625C602.375 702.406 513.25 736 416 736 186.25 736 0 549.75 0 320s186.25-416 416-416 416 186.25 416 416C832 382.719 817.75 442 792.875 495.312z" horiz-adv-x="832" />
+<glyph glyph-name="database" unicode="&#xf096;" d="M384-128C171.969-128 0-70.625 0 0c0 38.625 0 80.875 0 128 0 11.125 5.562 21.688 13.562 32C56.375 104.875 205.25 64 384 64s327.625 40.875 370.438 96c8-10.312 13.562-20.875 13.562-32 0-37.062 0-76.375 0-128C768-70.625 596-128 384-128zM384 128C171.969 128 0 185.375 0 256c0 38.656 0 80.844 0 128 0 6.781 2.562 13.375 6 19.906l0 0C7.938 408 10.5 412.031 13.562 416 56.375 360.906 205.25 320 384 320s327.625 40.906 370.438 96c3.062-3.969 5.625-8 7.562-12.094l0 0c3.438-6.531 6-13.125 6-19.906 0-37.062 0-76.344 0-128C768 185.375 596 128 384 128zM384 384C171.969 384 0 441.344 0 512c0 20.219 0 41.594 0 64 0 20.344 0 41.469 0 64C0 710.656 171.969 768 384 768c212 0 384-57.344 384-128 0-19.969 0-41.156 0-64 0-19.594 0-40.25 0-64C768 441.344 596 384 384 384zM384 704c-141.375 0-256-28.594-256-64s114.625-64 256-64 256 28.594 256 64S525.375 704 384 704z" horiz-adv-x="768" />
+<glyph glyph-name="device-camera" unicode="&#xf056;" d="M512 447.999c-70.691 0-127.999-57.308-127.999-127.999S441.309 192.00099999999998 512 192.00099999999998c5.713 0 11.337 0.38 16.852 1.105-46.344 7.058-81.851 47.079-81.851 95.394 0 53.295 43.204 96.499 96.499 96.499 48.314 0 88.336-35.507 95.394-81.851 0.726 5.515 1.105 11.139 1.105 16.852C639.999 390.691 582.691 447.999 512 447.999zM896 576H767.999L640 704H384L255.999 576H128c-35.348 0-64-28.652-64-64v-448c0-35.347 28.652-64 64-64h768c35.347 0 64 28.653 64 64V512C960 547.348 931.347 576 896 576zM416 640h192l64-64H352L416 640zM160.143 64C142.391 64 128 78.39099999999996 128 96.14300000000003V384h64v64h-64v31.857C128 497.609 142.391 512 160.143 512h182.526c-3.98-3.518-7.881-7.174-11.688-10.98-99.974-99.975-99.974-262.064 0-362.039l74.98-74.98H160.143zM512 128.00099999999998c-106.038 0-191.999 85.961-191.999 191.999S405.962 511.999 512 511.999 703.999 426.038 703.999 320 618.038 128.00099999999998 512 128.00099999999998zM832 352L681.327 512H832V352z" horiz-adv-x="1024" />
+<glyph glyph-name="device-camera-video" unicode="&#xf057;" d="M576 640c-35.347 0-64-28.653-64-64s28.653-64 64-64 64 28.653 64 64S611.347 640 576 640zM896 448L768 320v64c0 30.625-21.515 56.21-50.25 62.503C748.958 480.646 768 526.097 768 575.998 768 682.038 682.039 768 576 768c-101.123 0-183.986-78.178-191.45-177.393C350.516 621.306 305.442 640 256 640c-106.038 0-192-85.962-192-192.002C64 341.961 149.962 256 256 256h-64v-128h64v-128c0-35.347 28.653-64 64-64h384c35.347 0 64 28.653 64 64v64l128-128h64V448H896zM256 512c-35.347 0-64-28.653-64-64s28.653-64 64-64v-64c-70.692 0-128 57.308-128 127.999C128 518.692 185.308 576 256 576s128-57.307 128-128h-64C320 483.347 291.347 512 256 512zM576 128H448V256h128V128zM704 237.21299999999997c-33.526 33.547-70.276 70.317-73.373 73.414C624.837 316.418 616.837 320 608 320H416c-17.674 0-32-14.326-32-32v-192c0-8.329 3.183-15.915 8.396-21.607 0.53-0.58 39.123-39.164 74.409-74.393H352c-17.674 0-32 14.326-32 32V352c0 17.674 14.326 32 32 32h320c17.674 0 32-14.326 32-32V237.21299999999997zM576 448c-70.692 0-128 57.308-128 127.999C448 646.692 505.308 704 576 704s128-57.308 128-128.001C704 505.308 646.692 448 576 448zM896 128l-64 64 0.082 128.084L896 384.002V128z" horiz-adv-x="1024" />
+<glyph glyph-name="device-desktop" unicode="&#xf27c;" d="M960 768c-32 0-864 0-896 0s-64-32-64-64 0-544 0-576 32-64 64-64 320 0 320 0-192-64-192-128c0-32 32-64 64-64s480 0 512 0 64 32 64 64c0 64-192 128-192 128s288 0 320 0 64 32 64 64 0 544 0 576-32 64-64 64z m0-640h-896v576h896v-576z m-64 512h-192c-384-64-542-300-576-384v-64h768v448z" horiz-adv-x="1024" />
+<glyph glyph-name="device-mobile" unicode="&#xf038;" d="M576 832H64C28.688 832 0 803.312 0 768v-896c0-35.375 28.688-64 64-64h512c35.375 0 64 28.625 64 64V768C640 803.312 611.375 832 576 832zM288 768h64c17.625 0 32-14.344 32-32s-14.375-32-32-32h-64c-17.656 0-32 14.344-32 32S270.344 768 288 768zM352-128h-64c-17.656 0-32 14.375-32 32s14.344 32 32 32h64c17.625 0 32-14.375 32-32S369.625-128 352-128zM576 0H64V640h512V0z" horiz-adv-x="640" />
+<glyph glyph-name="diff" unicode="&#xf04d;" d="M448 576H320v-128H192v-128h128v-128h128V320h128V448H448V576zM192-64h384V64H192V-64zM640 832H128v-64h480l224-224v-608h64V576L640 832zM0 704v-896h768V512L576 704H0zM704-128H64V640h480l160-160V-128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-added" unicode="&#xf06b;" d="M512 512h-128v-128h-128v-128h128v-128h128v128h128v128h-128v128z m320 256c-32 0-736 0-768 0s-64-32-64-64 0-736 0-768 32-64 64-64 736 0 768 0 64 32 64 64 0 736 0 768-32 64-64 64z m-64-736c0-16-17-32-32-32s-558 0-576 0-32 12-32 32c0 16 0 560 0 576s16 32 32 32 561 0 576 0 32-16 32-32 0-560 0-576z" horiz-adv-x="896" />
+<glyph glyph-name="diff-ignored" unicode="&#xf099;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 194v-98h98l286 286v98h-98l-286-286z" horiz-adv-x="896" />
+<glyph glyph-name="diff-modified" unicode="&#xf06d;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 416c-71 0-128-57-128-128s57-128 128-128 128 57 128 128-57 128-128 128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-removed" unicode="&#xf06c;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-512 224h384v128h-384v-128z" horiz-adv-x="896" />
+<glyph glyph-name="diff-renamed" unicode="&#xf06e;" d="M832 768h-768c-32 0-64-32-64-64v-768c0-32 32-64 64-64h768c32 0 64 32 64 64v768c0 32-32 64-64 64z m-64-736c0-16-17-32-32-32h-576c-18 0-32 12-32 32v576c0 16 16 32 32 32h576c15 0 32-16 32-32v-576z m-320 352h-192v-128h192v-128l256 192-256 192v-128z" horiz-adv-x="896" />
+<glyph glyph-name="ellipsis" unicode="&#xf09a;" d="M640 512c-64 0-448 0-512 0s-128-64-128-128 0-64 0-128 64-128 128-128 448 0 512 0 128 64 128 128 0 64 0 128-64 128-128 128z m-384-256h-128v128h128v-128z m192 0h-128v128h128v-128z m192 0h-128v128h128v-128z" horiz-adv-x="768" />
+<glyph glyph-name="eye" unicode="&#xf04e;" d="M512 704c-192 0-416-128-512-384 96-192 288-320 512-320s416 128 512 320c-96 256-320 384-512 384z m0-640c-192 0-352 128-384 256 32 128 192 256 384 256s352-128 384-256c-32-128-192-256-384-256z m0 448c-20 0-38-4-56-9 33-15 56-48 56-87 0-53-43-96-96-96-39 0-72 23-87 56-5-18-9-36-9-56 0-106 86-192 192-192s192 86 192 192-86 192-192 192z" horiz-adv-x="1024" />
+<glyph glyph-name="file-binary" unicode="&#xf094;" d="M0-128V768h576l192-192v-704H0zM704 512L512 704H64v-768h640V512zM320 320H128V576h192V320zM256 512h-64v-128h64V512zM256 64h64v-64H128v64h64V192h-64v64h128V64zM512 384h64v-64H384v64h64V512h-64v64h128V384zM576 0H384V256h192V0zM512 192h-64v-128h64V192z" horiz-adv-x="768" />
+<glyph glyph-name="file-code" unicode="&#xf010;" d="M288 448L128 288l160-160 64 64-96 96 96 96L288 448zM416 384l96-96-96-96 64-64 160 160L480 448 416 384zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64z" horiz-adv-x="768" />
+<glyph glyph-name="file-directory" unicode="&#xf016;" d="M832 640c-32 0-336 0-352 0s-32 16-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-704 0-704h896s0 544 0 576-32 64-64 64z m-448 0h-320s0 15 0 32 16 32 32 32 241 0 256 0 32-15 32-32 0-32 0-32z" horiz-adv-x="896" />
+<glyph glyph-name="file-media" unicode="&#xf012;" d="M576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 576v-512h128c0 70.625 57.344 128 128 128-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128 0-70.625-57.375-128-128-128 70.625 0 128-57.375 128-128h128V448L512 576H128z" horiz-adv-x="768" />
+<glyph glyph-name="file-pdf" unicode="&#xf014;" d="M576 768H0v-896h768V576L576 768zM64 704h255.812c-13.188-4.094-27.281-15.031-34.625-42.875-13.25-49.406-7.031-130.75 15.625-209.344C276.688 370.562 178.188 175.125 171.531 163.5c-15.625-4.875-65.344-23.625-107.531-59.812V704zM347.125 396.531c57.625-149.781 95-149.531 135.188-167.594C398.344 216 334.219 206.75 249.781 169.5 246.094 163.062 326.281 315.40599999999995 347.125 396.531zM704-64H65.844 64v0.375c0.781-0.062 1.094-0.375 1.844-0.375 33.812 0 84.75 21 180.562 182.375 38.188 15.438 72.062 26.875 78.469 28.938 58.812 14.875 125 26.625 187.562 33.375C566.875 153.5 639.125 135 680.25 132.375c9.625-0.5 16.062 1.188 23.75 2V-64zM704 246.625c-23.688 14.688-54 25-89.125 25-24.25 0-50.625-1.375-78.688-4.375-26.938 13-92.562 32.719-147.188 190.219 17.094 103.625 12.719 173.562 12.719 173.562 6.781 52.938-23.344 72.844-51.625 72.844 0 0-0.279 0.125-0.344 0.125H512l192-192V246.625z" horiz-adv-x="768" />
+<glyph glyph-name="file-submodule" unicode="&#xf017;" d="M832 320c-32 0-192 0-192 0 0 32-32 64-64 64s-96 0-128 0-64-32-64-64 0-320 0-320h512s0 224 0 256-32 64-64 64z m-256-64h-128s0 17 0 32 15 32 32 32 48 0 64 0 32-15 32-32 0-32 0-32z m256 320c-32 0-336 0-352 0s-32 17-32 32 0 0 0 32-32 64-64 64-288 0-320 0-64-32-64-64 0-640 0-640h320s0 352 0 384 32 64 64 64 224 0 256 0 64-32 64-64h192s0 96 0 128-32 64-64 64z m-448 0h-320s0 16 0 32 16 32 32 32 240 0 256 0 32-17 32-32 0-32 0-32z" horiz-adv-x="896" />
+<glyph glyph-name="file-symlink-directory" unicode="&#xf0b1;" d="M832 640h-352c-16 0-32 16-32 32s0 0 0 32-32 64-64 64h-320c-32 0-64-32-64-64s0-704 0-704h896s0 544 0 576-32 64-64 64z m-768 32c0 17 16 32 32 32h256c15 0 32-15 32-32s0-32 0-32h-320s0 15 0 32z m384-544v128c-125 0-224-56-256-192 0 209 107 320 256 320 0 49 0 128 0 128l256-192-256-192z" horiz-adv-x="896" />
+<glyph glyph-name="file-symlink-file" unicode="&#xf0b0;" d="M576 768h-576v-896h768v704l-192 192z m128-832h-640v768h448l192-192v-576z m-320 448c-149 0-256-111-256-320 32 136 131 192 256 192v-128l256 192-256 192s0-79 0-128z" horiz-adv-x="768" />
+<glyph glyph-name="file-text" unicode="&#xf011;" d="M448 576H128v-64h320V576zM576 768H0v-896h768V576L576 768zM704-64H64V704h448l192-192V-64zM128 64h512v64H128V64zM128 192h512v64H128V192zM128 320h512v64H128V320z" horiz-adv-x="768" />
+<glyph glyph-name="file-zip" unicode="&#xf013;" d="M320 256v64h-64v-64H320zM320 384v64h-64v-64H320zM320 512v64h-64v-64H320zM192 448h64v64h-64V448zM576 768H0v-896h768V576L576 768zM704-64H64V704h192v-64h64v64h192l192-192V-64zM192 576h64v64h-64V576zM192 320h64v64h-64V320zM192 192l-64-64v-128h256V128l-64 64h-64v64h-64V192zM320 128v-64H192v64H320z" horiz-adv-x="768" />
+<glyph glyph-name="flame" unicode="&#xf0d2;" d="M433 787c50-134 24-207-32-265-61-64-156-112-223-206-89-125-104-400 217-472-135 71-164 277-18 406-38-125 32-205 119-176 85 29 141-32 139-102-1-48-20-89-69-112 209 37 293 210 293 342 0 174-155 198-77 344-93-8-125-69-116-169 6-66-63-111-114-81-41 25-40 73-4 109 77 76 107 251-115 382z" horiz-adv-x="1024" />
+<glyph glyph-name="fold" unicode="&#xf0cc;" d="M896 576H672l-64-64h192L672 384H224L96 512h192l-64 64H0v-63.999L160 352 0 192v-64h224l64 64H96l128 128h448l128-128H608l64-64h224v64L736 352l160 160.001V576zM640 640H512V832H384v-192H256l192-192L640 640zM256 64h128v-192h128V64h128L448 256 256 64z" horiz-adv-x="896" />
+<glyph glyph-name="gear" unicode="&#xf02f;" d="M447.938 482C358.531 482 286 409.469 286 320c0-89.375 72.531-162.062 161.938-162.062 89.438 0 161.438 72.688 161.438 162.062C609.375 409.469 537.375 482 447.938 482zM772.625 226.938l-29.188-70.312 52.062-102.25 6.875-13.5-72.188-72.188L611.75 24.625l-70.312-28.875L505.75-113.5l-4.562-14.5H399.156L355-4.687999999999988l-70.312 29-102.404-51.938-13.5-6.75-72.156 72.125 55.875 118.5-28.969 70.25L14.469 262.125 0 266.812V368.781L123.406 413l28.969 70.188-51.906 102.469-6.844 13.438 72.062 72.062 118.594-55.844 70.219 29.031 35.656 109.188L394.75 768h102l44.188-123.469 70.125-29.031L713.5 667.469l13.625 6.844 72.125-72.062-55.875-118.406L772.25 413.5l109.375-35.656L896 373.25v-101.938L772.625 226.938z" horiz-adv-x="896" />
+<glyph glyph-name="gift" unicode="&#xf042;" d="M448-128h320V192H448V-128zM64-128h320V192H64V-128zM447.75 455.812c31.469 3.5 66.875 7.406 87.375 9.719C619 474.875 694.5 550.406 703.812 634.25c9.312 83.75-51 144.125-134.688 134.719C503.688 761.656 443.844 714 416 653.625 388.156 714 328.312 761.656 262.906 769.031 179.188 778.375 118.781 718 128.188 634.25c9.344-83.844 84.875-159.312 168.656-168.719 20.531-2.312 55.938-6.281 87.406-9.719C383.75 451.594 384 448 384 448h64C448 448 448.25 451.594 447.75 455.812zM555.375 691.312c45.25 5.062 78-27.562 72.875-72.875-5-45.312-45.875-86.156-91.125-91.219-45.375-5.031-78 27.594-72.938 72.906C469.249 645.436 510.125 686.281 555.375 691.312zM294.906 527.219c-45.25 5.062-86.062 45.906-91.125 91.219-5.063 45.313 27.594 77.938 72.812 72.875 45.312-5.031 86.156-45.875 91.222-91.188C372.875 554.812 340.219 522.188 294.906 527.219zM448 448v-192h384V448H448zM0 256h384V448H0V256z" horiz-adv-x="896" />
+<glyph glyph-name="gist" unicode="&#xf00e;" d="M416 448l96-96-96-96 64-64 160 160-160 160-64-64z m-416 320v-832h768v832h-768z m704-768h-640v704h640v-704z m-352 256l-96 96 96 96-64 64-160-160 160-160 64 64z" horiz-adv-x="768" />
+<glyph glyph-name="gist-secret" unicode="&#xf08c;" d="M193 128l128-192h-256l-65 256 257 64-64-128z m448 128l64-128-128-192h256l64 256-256 64z m-84 0h-216l44-102-64-218h256l-64 218 44 102z m84 192h-384l-128-64h640l-128 64z m-64 256l-128-64-128 64-64-192h384l-64 192z" horiz-adv-x="896" />
+<glyph glyph-name="git-branch" unicode="&#xf020;" d="M512 640c-71 0-128-57-128-128 0-47 26-88 64-110v-18c0-64-64-128-128-128-53 0-95-11-128-29v303c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 34-13 64-34 87 19 23 49 41 98 41 128 0 256 128 256 256v18c38 22 64 63 64 110 0 71-57 128-128 128z m-384 64c35 0 64-29 64-64s-29-64-64-64-64 29-64 64 29 64 64 64z m0-768c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m384 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="640" />
+<glyph glyph-name="git-commit" unicode="&#xf01f;" d="M694.875 384C666.375 494.219 567.125 576 448 576c-119.094 0-218.375-81.781-246.906-192H0v-128h201.094C229.625 145.75 328.906 64 448 64c119.125 0 218.375 81.75 246.875 192H896V384H694.875zM448 192c-70.656 0-128 57.375-128 128 0 70.656 57.344 128 128 128 70.625 0 128-57.344 128-128C576 249.375 518.625 192 448 192z" horiz-adv-x="896" />
+<glyph glyph-name="git-compare" unicode="&#xf0ac;" d="M832 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-448 128s-32 0-64 0-64 32-64 64 0 402 0 402c38 22 64 63 64 110 0 71-57 128-128 128s-128-57-128-128c0-47 26-88 64-110 0 0 0-306 0-402s96-192 192-192c64 0 64 0 64 0v-128l192 192-192 192v-128z m-192 512c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="896" />
+<glyph glyph-name="git-merge" unicode="&#xf023;" d="M640 384c-47.625 0-88.625-26.312-110.625-64.906C523.625 319.5 518 320 512 320c-131.062 0-255.438 99.844-300.812 223.438C238.469 566.906 256 601.281 256 640c0 70.656-57.344 128-128 128S0 710.656 0 640c0-47.219 25.844-88.062 64-110.281V110.25C25.844 88.06200000000001 0 47.25 0 0c0-70.625 57.344-128 128-128s128 57.375 128 128c0 47.25-25.844 88.062-64 110.25V340.531C276.156 251.5 392.375 192 512 192c6.375 0 11.625 0.438 17.375 0.625C551.5 154.188 592.5 128 640 128c70.625 0 128 57.375 128 128C768 326.656 710.625 384 640 384zM128-64c-35.312 0-64 28.625-64 64 0 35.312 28.688 64 64 64 35.406 0 64-28.688 64-64C192-35.375 163.406-64 128-64zM128 576c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S163.406 576 128 576zM640 192c-35.312 0-64 28.625-64 64 0 35.406 28.688 64 64 64 35.375 0 64-28.594 64-64C704 220.625 675.375 192 640 192z" horiz-adv-x="768" />
+<glyph glyph-name="git-pull-request" unicode="&#xf009;" d="M704 110s0 306 0 402-96 192-192 192c-64 0-64 0-64 0v128l-192-192 192-192v128s32 0 64 0 64-32 64-64 0-402 0-402c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110z m-64-174c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m-512 832c-71 0-128-57-128-128 0-47 26-88 64-110v-419c-38-22-64-63-64-110 0-71 57-128 128-128s128 57 128 128c0 47-26 88-64 110v419c38 22 64 63 64 110 0 71-57 128-128 128z m0-832c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z m0 640c-35 0-64 29-64 64s29 64 64 64 64-29 64-64-29-64-64-64z" horiz-adv-x="768" />
+<glyph glyph-name="globe" unicode="&#xf0b6;" d="M512 704c-212.077 0-384-171.923-384-384s171.923-384 384-384c25.953 0 51.303 2.582 75.812 7.49-9.879 4.725-10.957 40.174-1.188 60.385 10.875 22.5 45 79.5 11.25 98.625s-24.375 27.75-45 49.875-12.19 25.451-13.5 31.125c-4.5 19.5 19.875 48.75 21 51.75s1.125 14.25 0.75 17.625S545.75 265.25 542 265.625s-5.625-6-10.875-6.375-28.125 13.875-33 17.625-7.125 12.75-13.875 19.5-7.5 1.5-18 5.625-44.25 16.5-70.125 27-28.125 25.219-28.5 35.625-15.75 25.5-22.961 36.375c-7.209 10.875-8.539 25.875-11.164 22.5s13.5-42.75 10.875-43.875-8.25 10.875-15.75 20.625 7.875 4.5-16.125 51.75 7.5 71.344 9 96 20.25-9 10.5 6.75 0.75 48.75-6.75 60.75S275 602 275 602c1.125 11.625 37.5 31.5 63.75 49.875s42.281 4.125 63.375-2.625 22.5-4.5 15.375 2.25 3 10.125 19.5 7.5 21-22.5 46.125-20.625 2.625-4.875 6-11.25-3.75-5.625-20.25-16.875S469.25 599 498.5 577.625s20.25 14.25 17.25 30S537.125 611 537.125 611c18-12 14.674-0.66 27.799-4.785S613.625 572 613.625 572c-44.625-24.375-16.5-27-9-32.625s-15.375-16.5-15.375-16.5c-9.375 9.375-10.875-0.375-16.875-3.75s-0.375-12-0.375-12c-31.031-4.875-24-37.5-23.625-45.375s-19.875-19.875-25.125-31.125S536.75 395 527 393.5s-19.5 36.75-72 22.5c-15.828-4.297-51-22.5-32.25-59.625s49.875 10.5 60.375 5.25-3-28.875-0.75-29.25 29.625-1.031 31.125-33 41.625-29.25 50.25-30 37.5 23.625 41.625 24.75S626 309.125 662 288.5s54.375-17.625 66.75-26.25 3.75-25.875 15.375-31.5 58.125 1.875 69.75-17.25-48-115.125-66.75-125.625S719.75 53.375 701 38s-45-34.406-69.75-49.125c-21.908-13.027-25.85-36.365-35.609-43.732C767.496-16.67999999999995 896 136.64999999999998 896 320 896 532.077 724.077 704 512 704zM602 343.625c-5.25-1.5-16.125-11.25-42.75 4.5s-45 12.75-47.25 15.375c0 0-2.25 6.375 9.375 7.5 23.871 2.311 54-22.125 60.75-22.5s10.125 6.75 22.125 2.883C616.25 347.52 607.25 345.125 602 343.625zM476.375 665.75c-2.615 1.902 2.166 4.092 5.016 7.875 1.645 2.186 0.425 5.815 2.484 7.875 5.625 5.625 33.375 13.5 27.949-1.875C506.4 664.25 480.5 662.75 476.375 665.75zM543.5 617c-9.375 0.375-31.443 2.707-27.375 6.75 15.844 15.75-6 20.25-19.5 21.375S477.5 653.75 484.25 654.5s33.75-0.375 38.25-4.125 28.875-13.5 30.375-20.625S552.875 616.625 543.5 617zM624.875 619.625c-7.5-6-45.24 21.529-52.5 27.75-31.5 27-48.375 18-54.99 22.5-6.617 4.5-4.26 10.5 5.865 19.5s38.625-3 55.125-4.875 35.625-14.625 36-29.781C614.75 639.564 632.375 625.625 624.875 619.625z" horiz-adv-x="1024" />
+<glyph glyph-name="graph" unicode="&#xf043;" d="M704 576H512v-640h192V576zM960 384H768v-448h192V384zM64-128V0h64v64H64V192h64v64H64V384h64v64H64V576h64v64H64V768h64V832H0v-1024h1024v64H64zM448 256H256v-320h192V256z" horiz-adv-x="1024" />
+<glyph glyph-name="heart" unicode="&#x2665;" d="M384-32c399 314 384 425 384 512s-72 192-192 192-192-128-192-128-72 128-192 128-192-105-192-192-15-198 384-512z" horiz-adv-x="768.199" />
+<glyph glyph-name="history" unicode="&#xf07e;" d="M448 768c-90.938 0-175.312-27.531-245.938-74.062L128 768v-256h256l-88 88c45.438 24.688 96.688 40 152 40 176.75 0 320-143.219 320-320 0-176.75-143.25-320-320-320-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.406C24.312 480.5 0 403.406 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM447.031 1L512 64V256h128l64 64-64 64H512l-64 64L320 320l64-64v-192L447.031 1z" horiz-adv-x="896" />
+<glyph glyph-name="home" unicode="&#xf08d;" d="M192 256l64-384h192V192h128v-320h192l64 384L512 576 192 256zM832 448V704H704l0.312-128.312L512 768 0 256h128l384 384 384-384h128L832 448z" horiz-adv-x="1024" />
+<glyph glyph-name="horizontal-rule" unicode="&#xf070;" d="M63.938 384h128v-128h64V639.938h-64V448h-128V639.938H0V256h63.938V384zM639.875 256V384h-63.938v-128H639.875zM639.875 448V575.938h-63.938V448H639.875zM447.938 448V575.938h128v64h-192V256h64V384h128v64H447.938zM0 0h639.875V128H0V0z" horiz-adv-x="639.875" />
+<glyph glyph-name="hourglass" unicode="&#xf09e;" d="M571 320c118 85 197 240 197 384 0 71-172 128-384 128s-384-57-384-128c0-144 80-299 197-384-118-85-197-240-197-384 0-71 172-128 384-128s384 57 384 128c0 144-80 299-197 384z m-187 448c141 0 256-29 256-64s-115-64-256-64-256 29-256 64 115 64 256 64z m-64-706c-154-7-238-40-253-82 16 114 75 189 141 251 73 68 112 60 112 103v-273z m-105 352c-70 55-122 130-142 215 70-32 183-53 311-53s241 21 311 53c-20-85-72-160-142-215-24 17-70 34-169 34s-145-17-169-34z m233-352v273c0-43 39-35 112-103 66-62 125-138 141-251-14 41-99 75-253 82z" horiz-adv-x="768" />
+<glyph glyph-name="hubot" unicode="&#xf09d;" d="M512 768c-283 0-512-229-512-512 0 0 0-192 0-256s64-128 128-128 704 0 768 0 128 64 128 128 0 256 0 256c0 283-229 512-512 512z m96-768h-192c-18 0-32 14-32 32s14 32 32 32h192c18 0 32-14 32-32s-14-32-32-32z m288 128c0-32-32-64-64-64s-128 0-128 0c0 32-32 64-64 64s-224 0-256 0-64-32-64-64c0 0-96 0-128 0s-64 32-64 64 0 360 0 360c78 129 220 216 384 216s306-87 384-216c0 0 0-328 0-360z m-128 384c-32 0-480 0-512 0s-64-32-64-64 0-96 0-128 32-64 64-64 480 0 512 0 64 32 64 64 0 96 0 128-32 64-64 64z m0-128l-64-64h-128l-64 64-64-64h-128l-64 64v64h64l64-64 64 64h128l64-64 64 64h64v-64z" horiz-adv-x="1024" />
+<glyph glyph-name="inbox" unicode="&#xf0cf;" d="M704 640H64L0 256v-256h768V256L704 640zM576 256l-64-128H256l-64 128H79l49 320h512l49-320H576z" horiz-adv-x="768" />
+<glyph glyph-name="info" unicode="&#xf059;" d="M448 448c35 0 64 29 64 64s-29 64-64 64-64-29-64-64 29-64 64-64z m0 320c-247 0-448-201-448-448s201-448 448-448 448 201 448 448-201 448-448 448z m0-768c-177 0-320 143-320 320s143 320 320 320 320-143 320-320-143-320-320-320z m64 320c0 32-32 64-64 64s-32 0-64 0-64-32-64-64h64s0-160 0-192 32-64 64-64 32 0 64 0 64 32 64 64h-64s0 160 0 192z" horiz-adv-x="896" />
+<glyph glyph-name="issue-closed" unicode="&#xf028;" d="M704 515.969l-96-96L768 256l256 256-96 96L769.25 449.219 704 515.969zM512 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 88.375 0 168.375-35.844 226.25-93.75l90.562 90.5C747.75 717.875 635.75 768 512 768 264.562 768 64 567.438 64 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448L759.75 119.75C768.688 130.75 684.75 0 512 0zM576 576H448v-320h128V576zM448 64h128V192H448V64z" horiz-adv-x="1024" />
+<glyph glyph-name="issue-opened" unicode="&#xf026;" d="M448 768C200.562 768 0 567.438 0 320c0-247.438 200.562-448 448-448 247.438 0 448 200.562 448 448C896 567.438 695.438 768 448 768zM448 0c-176.781 0-320 143.25-320 320 0 176.781 143.219 320 320 320 176.75 0 320-143.219 320-320C768 143.25 624.75 0 448 0zM384 64h128V192H384V64zM384 256h128V576H384V256z" horiz-adv-x="896" />
+<glyph glyph-name="issue-reopened" unicode="&#xf027;" d="M639.125 64.75C585.75 24.625 520 0 448 0c-176.781 0-320 143.25-320 320 0 45.562 9.781 88.781 27 128H64v99.469C24.312 480.562 0 403.406 0 320c0-247.438 200.562-448 448-448 107.375 0 204.5 39.312 281.75 102.25L768-64V128H576L639.125 64.75zM384 64h128V192H384V64zM512 576H384v-320h128V576zM896 320c0 247.438-200.562 448-448 448-107.406 0-204.531-39.312-281.656-102.344L128 704v-192h192l-63.156 63.156C310.281 615.312 376 640 448 640c176.75 0 320-143.219 320-320 0-45.562-9.75-88.75-27-128h91v-99.5C871.688 159.438 896 236.5 896 320z" horiz-adv-x="896" />
+<glyph glyph-name="jersey" unicode="&#xf019;" d="M704 832h-192c0-32-33-64-97-64s-95 32-95 64h-192c0-128-2-384-128-384 0 0-1-544-1-576s32-64 64-64 672 0 704 0 64 32 64 64 0 576 0 576c-126 0-128 256-128 384z m-609-960c-16 0-31 10-31 32 0 32 0 480 0 480 119 64 128 192 128 384h64c0-96 32-191 160-192s160 96 160 192h64c0-186 32-276 64-339v-557s-593 0-609 0z m385 576l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z m-352 320l-32-32v-320l32-32h128l32 32v320l-32 32h-128z m96-320h-64v256h64v-256z" horiz-adv-x="896" />
+<glyph glyph-name="jump-down" unicode="&#xf072;" d="M767.75 640H0.25L384 256.25 767.75 640zM0 128v-128h768V128H0z" horiz-adv-x="768" />
+<glyph glyph-name="jump-left" unicode="&#xf0a5;" d="M256.25 320L640-63.75v767.5L256.25 320zM0-64h128V704H0V-64z" horiz-adv-x="640" />
+<glyph glyph-name="jump-right" unicode="&#xf0a6;" d="M0-63.75L383.75 320 0 703.812V-63.75zM512 704v-768h128V704H512z" horiz-adv-x="640" />
+<glyph glyph-name="jump-up" unicode="&#xf073;" d="M0.188 0h767.5L384 383.75 0.188 0zM0 640v-128h768V640H0z" horiz-adv-x="768" />
+<glyph glyph-name="key" unicode="&#xf049;" d="M640.9 768.1c-141.4 0-256-114.6-256-256 0-19.6 2.2-38.6 6.4-56.9L0 64v-64l64-64h128l64 64v64h64v64h64v64h128l70.8 70.8c18.7-4.3 38.1-6.6 58.1-6.6 141.4 0 256 114.6 256 256S782.2 768.1 640.9 768.1zM384 320L64 0v64l320 320V320zM704 512c-35.3 0-64 28.7-64 64 0 35.3 28.7 64 64 64s64-28.7 64-64C768 540.7 739.3 512 704 512z" horiz-adv-x="896.9" />
+<glyph glyph-name="keyboard" unicode="&#xf00d;" d="M640 256h64V384h-64V256zM768 576h-64v-128h64V576zM640 576h-64v-128h64V576zM512 256h64V384h-64V256zM384 64h320V192H384V64zM768 256h128V576h-64v-192h-64V256zM256 64h64V192h-64V64zM768 64h128V192H768V64zM512 576h-64v-128h64V576zM192 384h-64v-128h64V384zM192 192h-64v-128h64V192zM0 704v-768h1024V704H0zM960 0H64V640h896V0zM384 256h64V384h-64V256zM256 576H128v-128h128V576zM384 576h-64v-128h64V576zM256 256h64V384h-64V256z" horiz-adv-x="1024" />
+<glyph glyph-name="law" unicode="&#xf0d8;" d="M514 640c34 1 61 28 62 62 1 37-29 67-66 66-34-1-61-28-62-62-1-37 29-67 66-66z m464-384h-18l-127 246c18 2 36 9 52 16 24 11 29 43 11 62l-1 1c-11 11-28 15-43 8-14-6-34-13-53-13-56 0-81 64-287 64s-231-64-287-64c-20 0-39 6-53 13-15 6-32 3-43-8l-1-1c-18-19-13-50 11-62 16-8 34-14 52-16l-127-246h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15h-18l-127 245c83 7 127 49 191 49v-486c-35 0-64-29-64-64h-71c-28 0-57-29-57-64h512c0 35-29 64-71 64h-57c0 35-29 64-64 64v486c64 0 108-42 191-49l-127-245h-18c-8 0-14-7-13-15 11-64 92-113 191-113s180 49 191 113c1 8-5 15-13 15z m-658 0h-192l96 180 96-180z m384 0l96 180 96-180h-192z" horiz-adv-x="1024" />
+<glyph glyph-name="light-bulb" unicode="&#xf000;" d="M512 768c-176.731 0-320-143.269-320-320 0-104.69 50.278-197.633 128-256.015V0c0-35.346 28.653-64 64-64 0-35.346 28.653-64 64-64h128c35.347 0 64 28.654 64 64 35.347 0 64 28.654 64 64V191.985C781.722 250.36699999999996 832 343.31 832 448 832 624.731 688.731 768 512 768zM640 32c0-17.673-14.326-32-32-32H416c-17.674 0-32 14.327-32 32v32h256V32zM704 278.693c-33.234-33.03-64-42.389-64-124.041V128h-64V256l128 128v64l-64 64-64-64-64 64-64-64-64 64-64-64v-64l128-128v-128h-64v26.652c0 81.652-30.766 91.011-64 124.041C280.177 323.82 256 383.082 256 448c0 141.385 114.615 256 256 256s256-114.615 256-256C768 383.082 743.823 323.82 704 278.693zM512 256L384 384v64l64-64 64 64 64-64 64 64v-64L512 256z" horiz-adv-x="1024" />
+<glyph glyph-name="link" unicode="&#xf05c;" d="M768 576h-138c48-32 93-89 107-128h30c65 0 128-64 128-128s-65-128-128-128h-192c-63 0-128 64-128 128 0 23 7 45 18 64h-137c-5-21-8-42-8-64 0-128 127-256 255-256s65 0 193 0 256 128 256 256-128 256-256 256z m-481-384h-30c-65 0-128 64-128 128s65 128 128 128h192c63 0 128-64 128-128 0-23-7-45-18-64h137c5 21 8 42 8 64 0 128-127 256-255 256s-65 0-193 0-256-128-256-256 128-256 256-256h138c-48 32-93 89-107 128z" horiz-adv-x="1024" />
+<glyph glyph-name="link-external" unicode="&#xf07f;" d="M640 64H128V574.094L256 576V704H0v-768h768V256H640V64zM384 704l128-128L320 384l128-128 192 192 128-128V704H384z" horiz-adv-x="768" />
+<glyph glyph-name="list-ordered" unicode="&#xf062;" d="M320 256h448v128h-448v-128z m0-256h448v128h-448v-128z m0 640v-128h448v128h-448z m-241-256h78v256h-36l-85-23v-50l43 2v-185z m110-206c0 36-12 78-96 78-33 0-64-6-83-16l1-66c21 10 42 15 67 15s32-11 32-28c0-26-30-58-110-112v-50h192v67l-91-2c49 30 87 66 87 113l1 1z" horiz-adv-x="768" />
+<glyph glyph-name="list-unordered" unicode="&#xf061;" d="M0 256h128v128h-128v-128z m0 256h128v128h-128v-128z m0-512h128v128h-128v-128z m256 256h512v128h-512v-128z m0 256h512v128h-512v-128z m0-512h512v128h-512v-128z" horiz-adv-x="768" />
+<glyph glyph-name="location" unicode="&#xf060;" d="M320 832c-177 0-320-143-320-320s160-416 320-704c160 288 320 527 320 704s-143 320-320 320z m0-448c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z" horiz-adv-x="640" />
+<glyph glyph-name="lock" unicode="&#xf06a;" d="M704 384c-32 0-64 0-64 0s0 64 0 192-128 256-256 256-256-128-256-256 0-192 0-192-32 0-64 0-64-32-64-64 0-416 0-448 32-64 64-64 608 0 640 0 64 32 64 64 0 416 0 448-32 64-64 64z m-192-128h-384v-64h384v-64h-384v-64h384v-64h-384v-64h384v-64h-448v448h448v-64z m0 128h-256s0 128 0 192 64 128 128 128 128-64 128-128 0-192 0-192z" horiz-adv-x="768" />
+<glyph glyph-name="logo-github" unicode="&#xf092;" d="M552.73 499.865H311.557c-6.205 0-11.25-5.045-11.25-11.297v-117.887c0-6.252 5.045-11.272 11.25-11.272h94.109v-146.542c0 0-21.145-7.057-79.496-7.057-68.914 0-165.156 25.244-165.156 236.795 0 211.642 100.197 239.491 194.307 239.491 81.465 0 116.514-14.304 138.869-21.241 7.01-2.203 13.404 4.831 13.404 11.105L534.543 785.87c0 2.912-1.041 6.417-4.262 8.785C521.186 801.048 465.865 832 326.168 832 165.133 832 0 763.513 0 434.243 0 105.02099999999996 189.051 56 348.381 56c131.883 0 212.021 56.314 212.021 56.314 3.268 1.801 3.6 6.395 3.6 8.479V488.568C563.955 494.773 558.887 499.865 552.73 499.865zM1772.381 803.866h-135.695c-6.252 0-11.271-5.044-11.271-11.296v-262.393h-211.619V792.57c0 6.252-5.068 11.296-11.178 11.296h-135.838c-6.111 0-11.084-5.044-11.084-11.296v-710.473c0-6.299 5.021-11.32 11.084-11.32h135.838c6.203 0 11.178 5.068 11.178 11.32V385.933h211.619l-0.475-303.883c0-6.3 5.021-11.272 11.084-11.272h135.885c6.252 0 11.131 5.068 11.131 11.272l0.473 710.521C1783.607 798.822 1778.539 803.866 1772.381 803.866zM714.949 787.763c-48.357 0-87.574-39.572-87.574-88.403 0-48.855 39.217-88.428 87.574-88.428s87.527 39.572 87.527 88.428C802.477 748.19 763.307 787.763 714.949 787.763zM792.861 559.874c0 6.205-5.02 11.344-11.131 11.344H646.32c-6.348 0-11.746-6.394-11.746-12.67 0 0 0-394.654 0-469.867 0-13.735 8.572-17.903 19.703-17.903 0 0 57.688 0 121.959 0 13.311 0 16.814 6.536 16.814 18.188-0.094 25.197-0.094 123.808-0.094 142.942C792.861 250.09500000000003 792.861 559.874 792.861 559.874zM2297.973 570.152h-134.701c-6.158 0-11.084-5.092-11.084-11.344v-348.31c0 0-34.244-25.197-82.934-25.197-48.547 0-61.525 22.024-61.525 69.719 0 47.553 0 303.835 0 303.835 0 6.252-5.068 11.345-11.131 11.345h-136.643c-6.252 0-11.178-5.093-11.178-11.345 0 0 0-185.521 0-326.807 0-141.284 78.766-175.906 186.99-175.906 88.854 0 160.609 49.115 160.609 49.115s3.363-25.766 5.068-28.844c1.422-3.078 5.447-6.158 9.852-6.158h86.58c6.158 0 11.178 5.069 11.178 11.321l0.379 477.278C2309.15 565.0609999999999 2304.129 570.152 2297.973 570.152zM2666.932 586.1610000000001c-76.539 0-128.592-34.148-128.592-34.148V792.57c0 6.252-5.068 11.296-11.131 11.296h-136.264c-6.109 0-11.131-5.044-11.131-11.296l-0.379-710.521c0-6.3 5.068-11.272 11.225-11.272 0 0 94.773 0 94.869 0 4.215 0 7.389 2.179 9.805 5.968 2.369 3.837 5.73 32.775 5.73 32.775s55.557-52.763 161.035-52.763c123.807 0 194.758 62.804 194.758 281.906C2856.859 557.482 2743.471 586.1610000000001 2666.932 586.1610000000001zM2613.791 185.77499999999998c-46.701 1.421-78.34 22.64-78.34 22.64v225.07c0 0 31.307 19.206 69.672 22.593 48.547 4.31 95.438-10.326 95.438-126.13C2700.322 207.94100000000003 2679.199 183.83399999999995 2613.791 185.77499999999998zM1185.125 188.33299999999997c-5.969 0-21.219-2.368-36.85-2.368-49.92 0-66.971 23.256-66.971 53.331 0 30.218 0 199.85 0 199.85h101.926c6.252 0 11.178 5.044 11.178 11.343v109.48c0.094 6.299-4.926 11.344-11.178 11.344h-101.926l-0.143 134.535c0 5.092-2.699 7.625-8.572 7.625H933.861c-5.352 0-8.336-2.391-8.336-7.578v-139.035c0 0-69.576-16.79-74.266-18.188-4.641-1.326-8.051-5.684-8.051-10.822v-87.408c0-6.252 5.068-11.344 11.178-11.344h71.139c0 0 0-91.34 0-210.222 0-156.109 109.553-171.455 183.439-171.455 33.723 0 74.076 10.988 80.848 13.356 4.074 1.421 6.395 5.637 6.395 10.136l0.047 96.101C1196.254 183.312 1190.998 188.428 1185.125 188.33299999999997z" horiz-adv-x="2856.857" />
+<glyph glyph-name="mail" unicode="&#xf03b;" d="M0 640v-640h896V640H0zM768 576L448 312 128 576H768zM64 512l252.031-191.625L64 128V512zM128 64l254 206.25L448 220l65.875 50.125L768 64H128zM832 128L579.625 320.062 832 512V128z" horiz-adv-x="896" />
+<glyph glyph-name="mail-read" unicode="&#xf03c;" d="M576 448H256v-64h320V448zM384 576H256v-64h128V576zM768 603.469V704H627.188L448 832 268.812 704H128v-100.531L0 512v-640h896V512L768 603.469zM192 640h512v-244.812L448 184 192 395.188V640zM64 384l252.031-191.625L64 0V384zM128-64l254 206.25L448 92l65.875 50.125L768-64H128zM832 0L579.625 192.062 832 384V0z" horiz-adv-x="896" />
+<glyph glyph-name="mail-reply" unicode="&#xf051;" d="M384 672l-384-288 384-288v192c111 0 329-61 384-280 0 291-196 451-384 472v192z" horiz-adv-x="768" />
+<glyph glyph-name="mark-github" unicode="&#xf00a;" d="M512 832C229.25 832 0 602.75 0 320c0-226.25 146.688-418.125 350.156-485.812 25.594-4.688 34.938 11.125 34.938 24.625 0 12.188-0.469 52.562-0.719 95.312C242-76.81200000000001 211.906 14.5 211.906 14.5c-23.312 59.125-56.844 74.875-56.844 74.875-46.531 31.75 3.53 31.125 3.53 31.125 51.406-3.562 78.47-52.75 78.47-52.75 45.688-78.25 119.875-55.625 149-42.5 4.654 33 17.904 55.625 32.5 68.375C304.906 106.56200000000001 185.344 150.5 185.344 346.688c0 55.938 19.969 101.562 52.656 137.406-5.219 13-22.844 65.094 5.062 135.562 0 0 42.938 13.75 140.812-52.5 40.812 11.406 84.594 17.031 128.125 17.219 43.5-0.188 87.312-5.875 128.188-17.281 97.688 66.312 140.688 52.5 140.688 52.5 28-70.531 10.375-122.562 5.125-135.5 32.812-35.844 52.625-81.469 52.625-137.406 0-196.688-119.75-240-233.812-252.688 18.438-15.875 34.75-47 34.75-94.75 0-68.438-0.688-123.625-0.688-140.5 0-13.625 9.312-29.562 35.25-24.562C877.438-98 1024 93.875 1024 320 1024 602.75 794.75 832 512 832z" horiz-adv-x="1024" />
+<glyph glyph-name="markdown" unicode="&#xf0c9;" d="M950.154 640H73.846C33.127 640 0 606.873 0 566.154v-492.308C0 33.125 33.127 0 73.846 0h876.308c40.721 0 73.846 33.125 73.846 73.846V566.154C1024 606.873 990.875 640 950.154 640zM576 128.125L448 128V320l-96-123.077L256 320v-192H128V512h128l96-128 96 128 128 0.125V128.125zM767.091 96.125L608 320h96V512h128v-192h96L767.091 96.125z" horiz-adv-x="1024" />
+<glyph glyph-name="megaphone" unicode="&#xf077;" d="M832 800c-130 0-124-130-704-128C57.344 672 0 557.375 0 416s57.344-256 128-256c22.781 0 43.188-0.5 64.188-0.875L256-128l192-32 64 96-45.125 203.125C709.375 102.875 733.75 32 832 32c106 0 192 172 192 384C1024 628.031 938 800 832 800zM197 349.062c-39.188 1.469-82.188 2.25-127.562 2.625C66 371.406 64 393.094 64 416c0 88.375 28.688 192 64 192 39.031-0.125 75 0.438 109 1.406C209.656 562.438 192 493.688 192 416 192 392.688 194.062 370.562 197 349.062zM261.312 346.062C258.125 368.312 256 391.625 256 416c0 79.5 18.438 149.5 46.906 196.219 155.156 8.312 251.906 28.469 319.031 50.188C593.625 595.531 576 510.344 576 416c0-40 3.875-78 9.5-114.312C513.344 320.375 412.812 337.406 261.312 346.062zM832 128c-12.125 0-23.688 5.062-34.812 12.125-15.25 67.312-83.438 418.344 117.438 494.188C942.125 581.5 960 503.812 960 416 960 257 902.625 128 832 128z" horiz-adv-x="1024" />
+<glyph glyph-name="mention" unicode="&#xf0be;" d="M466.697 732.899C238.66 760.898 31.1 598.735 3.102 370.698c-28-228.038 134.163-435.598 362.2-463.597 71.429-8.756 145.115 0.913 213.325 29.946l-0.016 0.032c24.404 10.357 35.788 38.538 25.431 62.939-10.359 24.403-38.538 35.787-62.94 25.43l-0.001 0.004c-52.472-22.339-109.15-29.799-164.1-23.067-175.413 21.538-300.153 181.2-278.616 356.613 21.538 175.413 181.199 300.154 356.613 278.616 175.412-21.538 300.154-181.199 278.617-356.612-4.309-35.083-21.542-55.725-61.6-55.725-42.5 0-64 45.889-64 81.222V432c0 26.51-21.49 48-48 48-9.699 0-18.72-2.887-26.269-7.833-25.684 20.259-57.437 33.87-94.349 38.402-105.246 12.923-201.045-61.924-213.967-167.17C212.508 238.15200000000004 287.354 142.35400000000004 392.6 129.43200000000002c57.379-7.045 116.216 14.707 157.871 53.13 24.959-28.124 59.866-47.624 100.121-52.567 87.707-10.769 167.537 51.602 178.307 139.309C856.898 497.34 694.734 704.899 466.697 732.899zM511.285 308.30100000000004c-6.462-52.623-54.361-90.047-106.985-83.585-52.623 6.461-90.046 54.36-83.585 106.984 6.461 52.623 54.361 90.046 106.984 83.585C480.322 408.823 517.746 360.924 511.285 308.30100000000004z" horiz-adv-x="832" />
+<glyph glyph-name="microscope" unicode="&#xf089;" d="M617-64c86.312 18.75 151 100 151 192 0 58.438-26.625 110.125-67.875 145.375C702.5 288.625 704 304.125 704 320c0 104.844-49.875 197.875-128 256l64 64v64l64 64L640 832l-64-64h-64L256 512l-128-64v-128l64-64h128l64 128 96 96c55.5-33.406 96-90.438 96-160-106.062 0-192-85.938-192-192H0v-64h192c19.125-14.25 42.062-22.125 64-32v-96H128L0-192h768L640-64H617zM512 128c0 35.375 28.625 64 64 64s64-28.625 64-64c0-35.312-28.625-64-64-64S512 92.68799999999999 512 128z" horiz-adv-x="768" />
+<glyph glyph-name="milestone" unicode="&#xf075;" d="M704 640H0v-256h704l128 128L704 640zM448 448H320V576h128V448zM448 832H320v-128h128V832zM320-192h128V320H320V-192z" horiz-adv-x="832" />
+<glyph glyph-name="mirror" unicode="&#xf024;" d="M320 512L128 320l192-192V256h384v-128l192 192L704 512v-128H320V512zM512 832L0 512v-704l512 256 512-256V512L512 832zM960-64L576 128v64H448v-64L64-64V448l384 256v-256h128V704l384-256V-64z" horiz-adv-x="1024" />
+<glyph glyph-name="mortar-board" unicode="&#xf0d7;" d="M501 244l-245 76s0-96 0-160 115-96 256-96 256 32 256 96 0 160 0 160l-245-76c-7-2-15-2-23 0h1z m18 409c-4 1-9 1-13 0l-489-152c-21-7-21-36 0-43l111-35v-113c-19-11-32-32-32-55 0-12 3-23 9-32-5-9-9-20-9-32v-165c0-35 128-35 128 0v165c0 12-3 23-9 32 5 9 9 20 9 32 0 24-13 44-32 55v93l313-98c4-1 9-1 13 0l489 152c21 7 21 36 0 43l-488 153z m-6-205c-35 0-64 14-64 32s29 32 64 32 64-14 64-32-29-32-64-32z" horiz-adv-x="1024" />
+<glyph glyph-name="move-down" unicode="&#xf0a8;" d="M640 512H448V832H192v-320H0l320-384L640 512zM0-192h640V0H0V-192z" horiz-adv-x="640" />
+<glyph glyph-name="move-left" unicode="&#xf074;" d="M0 0h192V640H0V0zM704 448V640L320 320l384-320V192h320V448H704z" horiz-adv-x="1024" />
+<glyph glyph-name="move-right" unicode="&#xf0a9;" d="M832 640v-640h192V640H832zM320 448H0v-256h320v-192l384 320L320 640V448z" horiz-adv-x="1024" />
+<glyph glyph-name="move-up" unicode="&#xf0a7;" d="M0 128h192v-320h256V128h192L320 512 0 128zM0 832v-192h640V832H0z" horiz-adv-x="640" />
+<glyph glyph-name="mute" unicode="&#xf080;" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM864 416l-64 64-96-96-96 96-63-63.5 95-96.5-96-96 64-64 96 96 96-96 64 64-96 96L864 416z" horiz-adv-x="896" />
+<glyph glyph-name="no-newline" unicode="&#xf09c;" d="M896 512v-128H768V512L576 320l192-192V256h192c0 0 64 0.375 64 64s0 192 0 192H896zM224 544C100.281 544 0 443.719 0 320c0-123.75 100.281-224 224-224s224 100.25 224 224C448 443.719 347.719 544 224 544zM96 320c0 70.656 57.344 128 128 128 18.75 0 36.406-4.219 52.469-11.531L107.531 267.5C100.219 283.625 96 301.25 96 320zM224 192c-18.75 0-36.406 4.25-52.469 11.5l168.938 168.969C347.781 356.406 352 338.75 352 320 352 249.375 294.656 192 224 192z" horiz-adv-x="1024" />
+<glyph glyph-name="octoface" unicode="&#xf008;" d="M940.812 554.312c8.25 20.219 35.375 101.75-8.562 211.906 0 0-67.375 21.312-219.875-82.906C648.5 700.875 579.875 703.5 512 703.5c-67.906 0-136.438-2.625-200.5-20.25C159.031 787.531 91.719 766.219 91.719 766.219 47.812 656 74.938 574.531 83.188 554.312 31.5 498.438 0 427.125 0 339.656 0 10.437999999999988 213.25-64 510.844-64 808.562-64 1024 10.437999999999988 1024 339.656 1024 427.125 992.5 498.438 940.812 554.312zM512-1c-211.406 0-382.781 9.875-382.781 214.688 0 48.938 24.062 94.595 65.344 132.312 68.75 62.969 185.281 29.688 317.438 29.688 132.25 0 248.625 33.281 317.438-29.625 41.312-37.78 65.438-83.312 65.438-132.312C894.875 8.875 723.375-1 512-1zM351.156 319.562c-42.469 0-76.906-51.062-76.906-114.188s34.438-114.312 76.906-114.312c42.375 0 76.812 51.188 76.812 114.312S393.531 319.562 351.156 319.562zM672.875 319.562C630.5 319.562 596 268.5 596 205.375s34.5-114.312 76.875-114.312 76.812 51.188 76.812 114.312C749.75 268.5 715.312 319.562 672.875 319.562z" horiz-adv-x="1024" />
+<glyph glyph-name="organization" unicode="&#xf037;" d="M768 448h-64H576h-64-64-64-64H192h-64C57.344 448 0 390.656 0 320v-64c0-47.25 25.844-88.062 64-110.25V-64h256v-128h256V-64h256V145.75c38.125 22.188 64 62.938 64 110.25v64C896 390.656 838.625 448 768 448zM256 0H128V256H64v64c0 35.312 28.688 64 64 64h81.719c-11-18.875-17.719-40.562-17.719-64v-128c0-47.25 25.844-88.062 64-110.25V0zM576 128V256h-64v-384H384V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64v-128C640 156.625 611.375 128 576 128zM832 256h-64v-256H640v81.75c38.125 22.188 64 62.938 64 110.25V320c0 23.438-6.75 45.125-17.75 64H768c35.375 0 64-28.688 64-64V256zM303.688 514.625C338.875 474.125 390.156 448 448 448c57.875 0 109.125 26.125 144.312 66.625C614.125 475.062 655.688 448 704 448c70.625 0 128 57.344 128 128s-57.375 128-128 128c-25.625 0-49.375-7.688-69.375-20.688C614.875 768.438 539.062 832 448 832S281.094 768.438 261.375 683.312C241.344 696.312 217.594 704 192 704c-70.656 0-128-57.344-128-128s57.344-128 128-128C240.312 448 281.844 475.062 303.688 514.625zM704 640c35.375 0 64-28.594 64-64s-28.625-64-64-64c-35.312 0-64 28.594-64 64S668.688 640 704 640zM448 768c70.625 0 128-57.344 128-128s-57.375-128-128-128c-70.656 0-128 57.344-128 128S377.344 768 448 768zM192 512c-35.312 0-64 28.594-64 64s28.688 64 64 64c35.406 0 64-28.594 64-64S227.406 512 192 512z" horiz-adv-x="896" />
+<glyph glyph-name="package" unicode="&#xf0c4;" d="M480 768L0 640v-576l480-128 480 128V640L480 768zM63.875 111.06600000000003L63.5 544l384.498-102.533 0.001-432.833L63.875 111.06600000000003zM63.5 608l160.254 42.734L640 539.735v-0.135l-160-42.667L63.5 608zM896.125 111.06600000000003L512.001 8.634000000000015l0.001 432.833L640 475.6v-156l128 34.135V509.733L896.5 544 896.125 111.06600000000003zM768 573.733v0.125L351.734 684.862 480 719.066 896.5 608 768 573.733z" horiz-adv-x="1024" />
+<glyph glyph-name="paintcan" unicode="&#xf0d1;" d="M384 832C171.923 832 0 660.077 0 448v-64c0-35.346 28.654-64 64-64v-320c0-70.692 143.269-128 320-128s320 57.308 320 128V320c35.346 0 64 28.654 64 64v64C768 660.077 596.077 832 384 832zM576 192v-32c0-17.673-14.327-32-32-32s-32 14.327-32 32v32c0 17.673-14.327 32-32 32s-32-14.327-32-32v-160c0-17.673-14.327-32-32-32s-32 14.327-32 32V160c0 17.673-14.327 32-32 32s-32-14.327-32-32v-32c0-35.346-28.654-64-64-64s-64 28.654-64 64v64c-35.346 0-64 28.654-64 64V371.193C186.382 340.108 279.318 320 384 320s197.618 20.108 256 51.193V256C640 220.654 611.346 192 576 192zM384 384c-107.433 0-199.393 26.474-237.372 64 37.979 37.526 129.939 64 237.372 64s199.393-26.474 237.372-64C583.393 410.474 491.433 384 384 384zM384 576c-176.62 0-319.816-57.236-319.996-127.867-0.001 0.001-0.002 0.001-0.003 0.002C64.075 624.804 207.314 768 384 768c176.731 0 320-143.269 320-320C704 518.692 560.731 576 384 576z" horiz-adv-x="768" />
+<glyph glyph-name="pencil" unicode="&#xf058;" d="M704 768L576 640l192-192 128 128L704 768zM0 64l0.688-192.562L192-128l512 512L512 576 0 64zM192-64H64V64h64v-64h64V-64z" horiz-adv-x="896" />
+<glyph glyph-name="person" unicode="&#xf018;" d="M448 640C448 746 362.062 832 256 832S64 746 64 640c0-106.062 85.938-192 192-192S448 533.938 448 640zM256 512c-70.656 0-128 57.344-128 128S185.344 768 256 768c70.625 0 128-57.344 128-128S326.625 512 256 512zM384 448H256 128C57.344 448 0 390.656 0 320v-128c0-70.625 57.344-128 128-128v-256h256V64c70.625 0 128 57.375 128 128V320C512 390.656 454.625 448 384 448zM448 192c0-35.375-28.625-64-64-64V256h-64v-384H192V256h-64v-128c-35.312 0-64 28.625-64 64V320c0 35.312 28.688 64 64 64h256c35.375 0 64-28.688 64-64V192z" horiz-adv-x="512" />
+<glyph glyph-name="pin" unicode="&#xf041;" d="M196 128l64-320 64 320c-20-2-43-3-64-3s-44 1-64 3z m254 299c-33 17-62 59-62 85v64c0 22 12 39 23 52 15 13 24 29 24 45 0 53-61 95-175 95s-175-42-175-95c0-16 9-32 24-45 11-13 23-30 23-52v-64c0-26-29-68-62-85-38-19-70-54-70-88 0-74 101-148 260-148s260 73 260 148c0 33-31 68-70 88z" horiz-adv-x="519.657" />
+<glyph glyph-name="playback-fast-forward" unicode="&#xf0bd;" d="M0 64l384 256L0 576V64zM768 320L384 576v-256-256L768 320z" horiz-adv-x="768" />
+<glyph glyph-name="playback-pause" unicode="&#xf0bb;" d="M0 0h192V640H0V0zM320 640v-640h192V640H320z" horiz-adv-x="512" />
+<glyph glyph-name="playback-play" unicode="&#xf0bf;" d="M0 640l512-320L0 0V640z" horiz-adv-x="512" />
+<glyph glyph-name="playback-rewind" unicode="&#xf0bc;" d="M384 320l384-256V576L384 320zM0 320l384-256V320 576L0 320z" horiz-adv-x="768" />
+<glyph glyph-name="plug" unicode="&#xf0d4;" d="M1003.386 627.336l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-45.707-45.707-90.51 90.51 45.707 45.707c24.744 24.744 24.744 64.861 0 89.605l-0.905 0.905c-24.744 24.744-64.861 24.744-89.605 0l-47.973-47.973C621.76 802.446 537.237 795.66 482.502 740.926l-24.89-24.89c-109.011-109.011-121.948-277.692-38.854-400.892l-4.138-4.138c-62.392-62.392-62.484-163.493-0.275-225.999 12.41-12.469 12.642-33.327 0.121-45.683-12.509-12.343-32.655-12.292-45.101 0.153l-89.427 89.427c-62.637 62.638-164.63 63.747-227.299 1.141-62.542-62.479-62.562-163.829-0.058-226.332l8.763-8.763c24.744-24.744 64.861-24.744 89.605 0l0.905 0.905c24.744 24.744 24.744 64.861 0 89.605l-8.292 8.292c-12.329 12.329-13.085 32.418-1.098 45.081 12.437 13.138 33.174 13.353 45.882 0.645l89.328-89.328c62.92-62.92 165.504-63.814 228.081-0.553 61.793 62.468 61.65 163.161-0.431 225.451-12.55 12.592-12.777 32.866-0.207 45.437l4.151 4.151c123.2-83.095 291.881-70.158 400.892 38.854l24.89 24.89c54.734 54.735 61.52 139.258 20.362 201.382l47.973 47.973C1028.129 562.475 1028.129 602.593 1003.386 627.336zM889.796 333.632c-37.49-37.49-98.274-37.49-135.765 0L527.757 559.906c-37.49 37.49-37.49 98.274 0 135.765 29.556 29.556 73.585 35.804 109.269 18.759l-41.839-41.839c-24.744-24.744-24.744-64.861 0-89.604l0.905-0.905c24.744-24.744 64.861-24.744 89.605 0l45.707 45.707 90.51-90.51-45.707-45.707c-24.744-24.744-24.744-64.861 0-89.605l0.905-0.905c24.744-24.744 64.861-24.744 89.604 0l41.839 41.839C925.6 407.218 919.351 363.188 889.796 333.632z" horiz-adv-x="1024" />
+<glyph glyph-name="plus" unicode="&#xf05d;" d="M384 384V640H256v-256H0v-128h256v-256h128V256h256V384H384z" horiz-adv-x="640" />
+<glyph glyph-name="podium" unicode="&#xf0af;" d="M320 832c-32 0-64-32-64-64s0-64 0-64h-64l-192-192v-128h192l64-384-128-64v-64h512v64l-128 64 64 384h192v128l-192 192h-256v64s14 0 32 0 32 17 32 32-16 32-32 32 0 0-32 0z m0-832l-53 320h118l-1-320h-64z m-224 512l128 128h32v-64h64v64h224l128-128h-576z" horiz-adv-x="768" />
+<glyph glyph-name="primitive-dot" unicode="&#xf052;" d="M-0.088 320c0 141.5 114.5 256 256 256 141.438 0 256-114.5 256-256s-114.562-256-256-256C114.413 64-0.088 178.5-0.088 320z" horiz-adv-x="511.825" />
+<glyph glyph-name="primitive-square" unicode="&#xf053;" d="M512 64H0V576h512V64z" horiz-adv-x="512" />
+<glyph glyph-name="pulse" unicode="&#xf085;" d="M736 320.062L563.188 486.406 422.406 288 352 729.594 152.438 320.062H0V192h230.406L288 307.188l57.594-345.562L576 288l102.375-96H896V320.062H736z" horiz-adv-x="896" />
+<glyph glyph-name="puzzle" unicode="&#xf0c0;" d="M755.75 256.85c-13.95 9.96-28.52 16.59-43.47 19.92-8.84 1.69-18.06 2.33-27.57 1.81-8.99-0.5-17.56-1.68-25.69-3.52-6.1-1.69-12.22-3.89-18.35-6.59-18.18-8.02-33.89-18.12-46.79-30.33-12.22-12.9-22.32-28.62-30.34-46.79-2.7-6.12-4.9-12.24-6.59-18.34-1.84-8.14-3.03-16.7-3.52-25.69-0.52-9.51 0.12-18.73 1.81-27.57 3.33-14.95 9.96-29.52 19.92-43.47 3.89-5.44 8.08-10.4 12.56-14.88 20.06-20.03 45.83-30.7 75.42-34.11 8.92-1.02 18.12-1.68 26.53-4.48 5.12-1.7 9.16-4.08 12.08-7.02 6.65-6.6 7.63-16.1 2.5-27.24-3.15-6.84-7.7-13.45-12.96-18.84l-2.79-2.86c-3.93-3.92-6.41-6.4-7.05-7.04-3.13-3.16-6.1-6.15-9.06-9.15l-2.96-2.92c-10.52-10.58-21.09-21.12-31.66-31.65-22.76-22.82-45.57-45.58-68.38-68.36-7.5-7.5-15-15-22.5-22.49-3.46-3.45-7.07-6.38-10.78-8.79-1.8-1.22-3.49-2.24-5.18-3.16-19.6-9.89-41.43-5.92-59.24 11.88-5.4 5.4-10.62 10.62-15.85 15.84-30.25 30.25-60.48 60.52-90.77 90.73-8.59 8.57-17.13 17.08-25.68 25.59-6.12 6.09-12.67 11.85-19.56 17.06-5.72 4.33-11.59 7.56-17.46 9.73-21.16 7.32-41.41 2.01-54.67-13.26-3.81-4.8-7-10.47-9.39-16.94-3.43-9.26-4.6-19.47-5.9-29.36-4.9-37.53-25.8-68.43-55.98-82.65-7.48-3.65-15.49-6.29-23.9-7.78-7.95-1.41-15.95-1.71-23.85-1.04-26.61 1.35-49.48 13.09-68.51 32.57-1.68 1.67-2.1 2.09-2.51 2.51-19.48 19.02-31.22 41.9-32.57 68.5-0.68 7.9-0.37 15.9 1.04 23.85 1.49 8.41 4.13 16.43 7.78 23.9 14.22 30.18 45.13 51.07 82.65 55.97 9.89 1.29 20.1 2.47 29.36 5.9 6.94 2.56 12.96 6.05 17.97 10.23 14.54 13.15 19.59 32.63 12.84 52.34-2.78 7.35-6 13.22-10.33 18.94-5.21 6.88-10.97 13.43-17.06 19.55-8.51 8.55-17.03 17.09-25.55 25.63-26.92 26.98-53.84 53.88-80.75 80.78l-10.03 10.03c-5.22 5.22-10.45 10.45-15.26 15.27-18.39 18.4-22.35 40.22-12.46 59.82 0.92 1.69 1.94 3.37 3.08 5.05 2.49 3.84 5.42 7.45 8.87 10.91 7.49 7.5 14.99 15 22.49 22.5 22.77 22.81 45.54 45.62 68.36 68.38 10.53 10.57 21.06 21.14 31.65 31.66l2.92 2.96c2.99 2.97 5.99 5.93 8.98 8.9 0.8 0.81 3.28 3.29 7.2 7.22l2.86 2.79c5.39 5.26 12 9.8 18.84 12.96 11.14 5.13 20.63 4.15 27.24-2.5 2.94-2.92 5.32-6.96 7.02-12.08 2.79-8.41 3.45-17.61 4.48-26.53 3.41-29.59 14.08-55.35 34.11-75.41 4.49-4.48 9.44-8.67 14.88-12.56 13.95-9.96 28.52-16.59 43.47-19.92 8.84-1.69 18.06-2.33 27.57-1.81 8.99 0.5 17.56 1.68 25.69 3.52 6.1 1.69 12.22 3.89 18.35 6.59 18.18 8.02 33.89 18.12 46.79 30.33 12.22 12.9 22.32 28.62 30.34 46.79 2.7 6.12 4.9 12.24 6.59 18.34 1.84 8.14 3.03 16.7 3.52 25.69 0.52 9.51-0.12 18.73-1.81 27.57-3.33 14.95-9.96 29.52-19.92 43.47-3.89 5.44-8.08 10.4-12.56 14.88-20.06 20.03-45.83 30.7-75.42 34.11-8.92 1.02-18.12 1.68-26.53 4.48-5.12 1.7-9.16 4.08-12.08 7.02-6.65 6.6-7.63 16.1-2.5 27.24 3.15 6.84 7.7 13.45 12.96 18.84l2.79 2.86c3.93 3.92 6.41 6.4 7.05 7.04 3.13 3.16 6.1 6.15 9.06 9.15l2.96 2.92c10.52 10.58 21.09 21.12 31.66 31.65 22.76 22.82 45.57 45.58 68.38 68.35 7.5 7.5 15 15 22.5 22.49 3.46 3.45 7.07 6.38 10.78 8.79 1.8 1.22 3.49 2.24 5.18 3.16 19.6 9.89 41.43 5.92 59.24-11.88 5.4-5.4 10.62-10.62 15.85-15.84 30.25-30.25 60.48-60.52 90.77-90.73 8.59-8.57 17.13-17.08 25.68-25.59 6.12-6.09 12.67-11.85 19.56-17.06 5.72-4.33 11.59-7.56 17.46-9.73 21.16-7.32 41.41-2.01 54.67 13.26 3.81 4.8 7 10.47 9.39 16.94 3.43 9.26 4.6 19.47 5.9 29.36 4.9 37.53 25.8 68.43 55.98 82.65 7.48 3.65 15.49 6.28 23.9 7.78 7.95 1.41 15.95 1.71 23.85 1.04 26.61-1.35 49.48-13.09 68.51-32.57 1.68-1.67 2.1-2.09 2.51-2.51 19.48-19.02 31.22-41.9 32.57-68.5 0.68-7.9 0.37-15.9-1.04-23.85-1.49-8.41-4.13-16.43-7.78-23.9-14.22-30.18-45.13-51.07-82.65-55.97-9.89-1.29-20.1-2.47-29.36-5.9-6.94-2.56-12.96-6.05-17.97-10.23-14.54-13.15-19.59-32.63-12.84-52.34 2.78-7.35 6-13.22 10.33-18.94 5.21-6.88 10.97-13.43 17.06-19.55 8.51-8.55 17.03-17.09 25.55-25.63 30.26-30.33 60.54-60.56 90.78-90.81 5.22-5.22 10.45-10.45 15.26-15.27 18.39-18.4 22.35-40.22 12.46-59.82-0.92-1.69-1.94-3.37-3.08-5.05-2.49-3.84-5.42-7.45-8.87-10.91-7.49-7.5-14.99-15-22.49-22.5-22.77-22.81-45.54-45.62-68.36-68.38-10.53-10.57-21.06-21.14-31.65-31.66l-2.92-2.96c-2.99-2.97-5.99-5.93-8.98-8.9-0.8-0.81-3.28-3.29-7.2-7.22l-2.86-2.79c-5.39-5.26-12-9.8-18.84-12.96-11.14-5.13-20.63-4.15-27.24 2.5-2.94 2.92-5.32 6.96-7.02 12.08-2.79 8.41-3.45 17.61-4.48 26.53-3.41 29.59-14.08 55.35-34.11 75.41C766.15 248.76999999999998 761.19 252.97000000000003 755.75 256.85z" horiz-adv-x="1024" />
+<glyph glyph-name="question" unicode="&#xf02c;" d="M448 64h128v128h-128v-128z m64 512c-96 0-192-96-192-192h128c0 32 32 64 64 64s64-32 64-64c0-64-128-64-128-128h128c64 22 128 64 128 160s-96 160-192 160z m0 256c-283 0-512-229-512-512s229-512 512-512 512 229 512 512-229 512-512 512z m0-896c-212 0-384 172-384 384s172 384 384 384 384-172 384-384-172-384-384-384z" horiz-adv-x="1024" />
+<glyph glyph-name="quote" unicode="&#xf063;" d="M0 320v-256h256V320H128c0 0 0 128 128 128V576C256 576 0 576 0 320zM640 448V576c0 0-256 0-256-256v-256h256V320H512C512 320 512 448 640 448z" horiz-adv-x="640" />
+<glyph glyph-name="radio-tower" unicode="&#xf030;" d="M306.838 441.261c15.868 16.306 15.868 42.731 0 59.037-20.521 21.116-30.643 48.417-30.705 76.124 0.062 27.77 10.183 55.039 30.705 76.186 15.868 16.337 15.868 42.764 0 59.069-7.934 8.184-18.272 12.275-28.706 12.275-10.371 0-20.804-4.029-28.738-12.213-36.266-37.297-54.633-86.433-54.57-135.317-0.062-48.792 18.305-97.927 54.57-135.161C265.262 424.955 290.97 424.955 306.838 441.261zM149.093 798.858c-8.121 8.309-18.68 12.463-29.3 12.463-10.558 0-21.179-4.154-29.237-12.463C30.8 737.509 0.751 656.856 0.813 576.422 0.751 496.081 30.8 415.272 90.494 353.985c16.181-16.618 42.356-16.618 58.537 0 16.118 16.587 16.118 43.513 0 60.067-43.7 44.98-65.44 103.456-65.44 162.368s21.74 117.449 65.44 162.368C165.149 755.439 165.149 782.365 149.093 798.858zM513.031 472.153c57.351 0 103.956 46.574 103.956 103.956 0 57.382-46.605 103.955-103.956 103.955-57.381 0-103.956-46.573-103.956-103.955C409.076 518.727 455.65 472.153 513.031 472.153zM933.539 798.233c-16.181 16.618-42.355 16.618-58.475 0-16.181-16.587-16.181-43.513 0-60.068 43.668-44.918 65.409-103.456 65.409-162.368 0-58.85-21.805-117.387-65.473-162.306-16.117-16.618-16.117-43.575 0.062-60.068 8.059-8.309 18.616-12.463 29.237-12.463 10.558 0 21.178 4.154 29.236 12.463 59.726 61.287 89.774 142.096 89.649 222.437C1023.313 656.138 993.264 736.947 933.539 798.233zM513.281 389.127L513.281 389.127c-26.489-0.062-53.04 6.466-77.091 19.429L235.057-127.59000000000003h95.209l54.819 63.973h255.891l53.977-63.973h95.272L589.124 408.431C565.384 395.655 539.395 389.127 513.281 389.127zM512.656 358.483L577.004 128.29999999999995H449.059L512.656 358.483zM385.086 0.3550000000000182l63.974 63.973h127.944l63.974-63.973H385.086zM717.194 710.958c-15.868-16.306-15.868-42.731 0-59.037 20.491-21.116 30.611-48.511 30.674-76.124-0.062-27.77-10.183-55.102-30.674-76.187-15.868-16.336-15.868-42.763 0-59.068 7.871-8.184 18.242-12.213 28.737-12.213 10.309 0 20.741 4.029 28.675 12.213 36.298 37.234 54.665 86.433 54.54 135.255 0.125 48.792-18.181 97.927-54.54 135.161C758.801 727.264 733.062 727.264 717.194 710.958z" horiz-adv-x="1024" />
+<glyph glyph-name="repo" unicode="&#xf001;" d="M320 576h-64v-64h64v64z m0 128h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 736 0 768-32 64-64 64z m0-800c0-16-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 17-32 32 0 96 0 96h640s0-80 0-96z m0 160h-512v576h513l-1-576z m-384 128h-64v-64h64v64z m0 128h-64v-64h64v64z" horiz-adv-x="768" />
+<glyph glyph-name="repo-clone" unicode="&#xf04c;" d="M320 448h-64v-64h64v64z m-128 320h256v64s-352 0-384 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s286 0 320 0 64 32 64 64 0 192 0 192h-576v576z m512-640s0-79 0-96-14-32-32-32-288 0-288 0v64h-192v-64s-80 0-96 0-32 16-32 32 0 96 0 96h640z m-384 448h-64v-64h64v64z m-64-320h64v64h-64v-64z m704 576c-32 0-288 0-320 0s-64-32-64-64 0-352 0-384 32-64 64-64 64 0 64 0v-64l32 32 32-32v64s160 0 192 0 64 32 64 64 0 352 0 384-32 64-64 64z m-256-448s-15 0-32 0-32 15-32 32 0 32 0 32h64v-64z m256 32c0-16-15-32-32-32s-160 0-160 0v64h192s0-16 0-32z m0 96h-256v256h224s32 0 32-32 0-224 0-224z m-640 192h-64v-64h64v64z" horiz-adv-x="1024" />
+<glyph glyph-name="repo-force-push" unicode="&#xf04a;" d="M768 768c0 32-32 64-64 64s-608 0-640 0-64-32-64-64 0-768 0-768 0 32 0 0 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 15-32 32 0 96 0 96h256v64h-128v576h512v-576h-128v-64h128s0-80 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768z m-272-320h144l-192 256-192-256h144l-144-192h128v-448h128v448h128l-144 192z" horiz-adv-x="767.896" />
+<glyph glyph-name="repo-forked" unicode="&#xf002;" d="M768 704c0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-106l-192-212-192 212v106c38 22 64 63 64 111 0 71-57 128-128 128s-128-57-128-128c0-47 26-89 64-111v-156l256-282v-109c-38-22-64-63-64-111 0-71 57-128 128-128s128 57 128 128c0 47-26 89-64 111v109l256 282v156c38 22 64 63 64 111z m-640 63c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z m256-891c-34 0-62 28-62 62s28 62 62 62 62-28 62-62-28-62-62-62z m256 891c34 0 62-28 62-62s-28-62-62-62-62 28-62 62 28 62 62 62z" horiz-adv-x="768" />
+<glyph glyph-name="repo-pull" unicode="&#xf006;" d="M1024 512l-192 192v-128h-384v-128h384v-128l192 192z m-320-320h-512v576h512v-128h64s0 96 0 128-32 64-64 64-608 0-640 0-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l96 96 96-96v128s288 0 320 0 64 32 64 64 0 384 0 384h-64v-192z m0-160c0-15-15-32-32-32s-288 0-288 0v64h-192v-64s-79 0-96 0-32 16-32 32 0 96 0 96h640s0-81 0-96z m-384 544h-64v-64h64v64z m0 128h-64v-64h64v64z m0-256h-64v-64h64v64z m-64-192h64v64h-64v-64z" horiz-adv-x="1024" />
+<glyph glyph-name="repo-push" unicode="&#xf005;" d="M448 512l-192-256h128v-448h128v448h128l-192 256z m-192 0h64v64h-64v-64z m64 192h-64v-64h64v64z m384 128c-32 0-608 0-640 0s-64-32-64-64 0-736 0-768 32-64 64-64 128 0 128 0v-128l128 128v128h-128v-64s-79 0-96 0-32 14-32 32 0 96 0 96h256v64h-128v576h513l-1-576h-128v-64h128s0-79 0-96-15-32-32-32-96 0-96 0v-64s96 0 128 0 64 32 64 64 0 736 0 768-32 64-64 64z" horiz-adv-x="768" />
+<glyph glyph-name="rocket" unicode="&#xf033;" d="M716.737 707.944c-71.926-41.686-148.041-96.13-218.436-166.555-45-45.031-81.213-88.78-110.39-129.778L209.538 378.65 0.047 169.00300000000004l186.818-5.815 131.562 131.562c-46.439-96.224-50.536-160.019-50.536-160.019l58.854-58.792c0 0 65.827 6.255 162.737 53.163L355.107-5.119000000000028l5.88-186.881 209.585 209.521 33.086 179.252c41.403 29.02 85.185 65.046 129.716 109.545 70.425 70.455 124.837 146.541 166.555 218.466-45.97 9.351-88.125 28.488-121.397 61.668C745.257 619.819 725.994 661.975 716.737 707.944zM786.161 745.157c5.004-45 19.952-81.274 44.78-105.98 24.769-24.985 60.98-39.902 106.138-44.844C1003.063 727.677 1023.953 832 1023.953 832S919.63 811.142 786.161 745.157z" horiz-adv-x="1024" />
+<glyph glyph-name="rss" unicode="&#xf034;" d="M128 192C57.344 192 0 134.625 0 64s57.344-128 128-128 128 57.375 128 128S198.656 192 128 192zM128 448c0 0-64-2-64-64s64-64 64-64c141.375 0 256-114.625 256-256 0 0 0-64 64-64s64 64 64 64C512 276 340.031 448 128 448zM128 704c0 0-64 0-64-64s64-64 64-64c282.75 0 512-229.25 512-512 0 0 0-64 64-64s64 64 64 64C768 417.406 481.5 704 128 704z" horiz-adv-x="768" />
+<glyph glyph-name="ruby" unicode="&#xf047;" d="M768 704H256L0 448l512-512 512 512L768 704zM128 448l192 192h384l192-192L512 64 128 448zM704 576H512v-448l320 320L704 576z" horiz-adv-x="1024" />
+<glyph glyph-name="screen-full" unicode="&#xf066;" d="M128 64h639.875V576H128V64zM255.938 448h384v-256h-384V448zM64 639.938h191.938v64H0V448h64V639.938zM64 192H0v-255.938h255.938V0H64V192zM639.938 703.938v-64h191.938V448h64V703.938H639.938zM831.875 0H639.938v-63.938h255.938V192h-64V0z" horiz-adv-x="895.875" />
+<glyph glyph-name="screen-normal" unicode="&#xf067;" d="M127.938 640.062H0v-64h191.938V768h-64V640.062zM0-0.06200000000001182h127.938V-128h64V63.93799999999999H0V-0.06200000000001182zM768.062 640.062V768h-64v-191.938H896v64H768.062zM704.062-128h64V-0.06200000000001182H896v64H704.062V-128zM192.062 128H704V512H192.062V128zM320 384h256v-128H320V384z" horiz-adv-x="896" />
+<glyph glyph-name="search" unicode="&#xf02e;" d="M960 0L710.875 249.125C746.438 307.188 768 374.844 768 448 768 660.031 596 832 384 832 171.969 832 0 660.031 0 448c0-212 171.969-384 384-384 73.156 0 140.812 21.562 198.875 57L832-128c17.5-17.5 46.5-17.375 64 0l64 64C977.5-46.5 977.5-17.5 960 0zM384 192c-141.375 0-256 114.625-256 256s114.625 256 256 256 256-114.625 256-256S525.375 192 384 192z" horiz-adv-x="973.125" />
+<glyph glyph-name="server" unicode="&#xf097;" d="M704 448h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192-128h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 832h-640c-35 0-64-32-64-64v-128c0-32 32-64 64-64h640c32 0 64 32 64 64v128c0 32-32 64-64 64z m-576-192h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m128 0h-64v128h64v-128z m192 64h-64v64h64v-64z" horiz-adv-x="768" />
+<glyph glyph-name="settings" unicode="&#xf07c;" d="M64-64h128V128H64V-64zM192 704H64v-320h128V704zM512 704H384v-128h128V704zM0 192h256V320H0V192zM384-64h128V320H384V-64zM320 384h256V512H320V384zM832 704H704v-384h128V704zM640 256v-128h256V256H640zM704-64h128V64H704V-64z" horiz-adv-x="896" />
+<glyph glyph-name="sign-in" unicode="&#xf036;" d="M640 256L640 384 896 384 896 512 640 512 640 640 448 496 448 640 192 768 704 768 704 576 768 576 768 832 64 832 64 0 448-192 448 0 768 0 768 320 704 320 704 64 448 64 448 400z" horiz-adv-x="896" />
+<glyph glyph-name="sign-out" unicode="&#xf032;" d="M640 64H384V640L128 768h512v-192h64V832H0v-832l384-192V0h320V320h-64V64zM1024 448L768 640v-128H512v-128h256v-128L1024 448z" horiz-adv-x="1024" />
+<glyph glyph-name="split" unicode="&#xf0c6;" d="M448 576l-256 256-192-192 311-300c15 81 43 136 133 230l5 6z m128 256l133-133-197-197c-99-99-128-162-128-309v-384h256v384c0 52 19 94 53 128l197 197 133-133v448h-448z" horiz-adv-x="1024" />
+<glyph glyph-name="squirrel" unicode="&#xf0b2;" d="M768 768c-141.385 0-256-83.75-256-186.875C512 457.25 544 387 512 192c0 288-177 405.783-256 405.783 3.266 32.17-30.955 42.217-30.955 42.217s-14-7.124-19.354-21.583c-17.231 20.053-36.154 17.54-36.154 17.54l-8.491-37.081c0 0-117.045-40.876-118.635-206.292C56 371 141.311 353.898 201.887 364.882c57.157-2.956 42.991-50.648 30.193-63.446C178.083 247.438 128 320 64 320s-64-64 0-64 64-64 192-64c-198-77 0-256 0-256h-64c-64 0-64-64-64-64s256 0 384 0c192 0 320 64 320 222.182 0 54.34-27.699 114.629-64 162.228C697.057 349.433 782.453 427.566 832 384s192-64 192 128C1024 653.385 909.385 768 768 768zM160 448c-17.674 0-32 14.327-32 32 0 17.674 14.326 32 32 32 17.673 0 32-14.326 32-32C192 462.327 177.673 448 160 448z" horiz-adv-x="1024" />
+<glyph glyph-name="star" unicode="&#xf02a;" d="M896 448l-313.5 40.781L448 768 313.469 488.781 0 448l230.469-208.875L171-63.93799999999999l277 148.812 277.062-148.812L665.5 239.125 896 448z" horiz-adv-x="896" />
+<glyph glyph-name="steps" unicode="&#xf0c7;" d="M136 768C60.89 768 0 667.71 0 544c0-68.83 17.02-141.84 34-254.54C47.3 201.16999999999996 79.67 128 136 128s94.08 48.79 94.08 137.97c0 30.37-24.97 78.75-26.08 120.03-2.02 74.46 49.93 104.17 49.93 173C253.93 682.71 211.1 768 136 768zM502.97 512c-75.1 0-117.93-85.29-117.93-209 0-68.83 51.95-98.54 49.93-173-1.109-41.28-26.08-89.66-26.08-120.03 0-89.18 37.75-137.97 94.08-137.97s88.7 73.17 102 161.46c16.98 112.7 34 185.71 34 254.54C638.97 411.71 578.08 512 502.97 512z" horiz-adv-x="640" />
+<glyph glyph-name="stop" unicode="&#xf08f;" d="M704 832H320L0 512v-384l320-320h384l320 320V512L704 832zM896 192L640-64H384L128 192V448l256 256h256l256-256V192zM448 256h128V576H448V256zM448 64h128V192H448V64z" horiz-adv-x="1024" />
+<glyph glyph-name="sync" unicode="&#xf087;" d="M655.461 358.531c11.875-81.719-13.062-167.781-76.812-230.594-94.188-92.938-239.5-104.375-346.375-34.562l74.875 73L31.96 204.75 70.367-64l84.031 80.5c150.907-111.25 364.938-100.75 502.063 34.562 79.5 78.438 115.75 182.562 111.25 285.312L655.461 358.531zM189.46 511.938c94.156 92.938 239.438 104.438 346.313 34.562l-75-72.969 275.188-38.406L697.586 704l-83.938-80.688C462.711 734.656 248.742 724.031 111.585 588.75 32.085 510.344-4.133 406.219 0.335 303.5l112.25-22.125C100.71 363.125 125.71 449.094 189.46 511.938z" horiz-adv-x="768.051" />
+<glyph glyph-name="tag" unicode="&#xf015;" d="M384 768H128L0 640v-256l512-512 384 384L384 768zM64 416V608l96 96h192l448-448L512-32 64 416zM448 512L256 320l256-256 192 192L448 512zM352 320l96 96 160-160-96-96L352 320zM320 544c0 53-43 96-96 96s-96-43-96-96 43-96 96-96S320 491 320 544zM224 512c-17.656 0-32 14.344-32 32s14.344 32 32 32 32-14.344 32-32S241.656 512 224 512z" horiz-adv-x="896" />
+<glyph glyph-name="telescope" unicode="&#xf088;" d="M76 409c32 8 229 59 229 59-1-6-2-19-2-19 0-71 49-128 128-128s128 59 128 128c0 11-8 22-19 32l49-3s7 2 31 8c-51-14-108 31-126 99s8 135 60 149c-24-6-31-8-31-8l-168-110c-34-9-55-46-46-80 2-9 7-17 12-23-7-12-12-26-15-40-27 1-51 19-59 46-9 34 11 69 45 78l-245-65c-34-9-54-43-45-77s41-54 73-46z m419-153h-128v-64l-320-320h128l192 128v-128h128v128l192-128h128l-320 320v64z m429 448c-18 68-70 110-122 96-69-18-98-28-186-51-51-14-79-80-61-148s74-115 125-102c87 23 117 33 186 51 51 14 76 85 58 154z m-70-90c-17-5-42 17-51 51s-4 66 13 70 42-17 51-51 4-66-13-70z" horiz-adv-x="929.875" />
+<glyph glyph-name="terminal" unicode="&#xf0c8;" d="M831 705H63c-35.35 0-64-28.65-64-64v-640c0-35.35 28.65-64 64-64h768c35.35 0 64 28.65 64 64V641C895 676.35 866.35 705 831 705zM127 257l128 128L127 513l64 64 192-192L191 193 127 257zM639 193H383v64h256V193z" horiz-adv-x="896" />
+<glyph glyph-name="three-bars" unicode="&#xf05e;" d="M0 640v-128h768v128h-768z m0-384h768v128h-768v-128z m0-256h768v128h-768v-128z" horiz-adv-x="768" />
+<glyph glyph-name="tools" unicode="&#xf031;" d="M286.547 366.984c16.843-16.812 81.716-85.279 81.716-85.279l35.968 37.093-56.373 58.248L456.072 491.98c0 0-48.842 47.623-27.468 28.655 20.438 75.903 1.812 160.589-55.842 220.243C315.608 800.064 234.392 819.47 161.425 799.096l123.653-127.715-32.53-125.309-121.06-33.438L7.898 640.3820000000001c-19.718-75.436-0.969-159.339 56.311-218.556C124.302 359.703 210.83 341.453 286.547 366.984zM698.815 242.769L549.694 95.46100000000001l245.932-254.805c20.062-20.812 46.498-31.188 72.872-31.188 26.25 0 52.624 10.375 72.811 31.188 40.249 41.624 40.249 108.997 0 150.62L698.815 242.769zM1023.681 670.162L867.06 832.001 405.387 354.703l56.373-58.248L185.425 10.839000000000055l-63.154-33.749-89.217-145.559 22.719-23.562 140.839 92.247 32.655 65.312 276.336 285.554 56.404-58.248L1023.681 670.162z" horiz-adv-x="1024" />
+<glyph glyph-name="trashcan" unicode="&#xf0d0;" d="M704 704H448c0 0 0 24.057 0 32 0 17.673-14.327 32-32 32s-32-14.327-32-32c0-17.673 0-32 0-32H128c-35.346 0-64-28.654-64-64v-64c0-35.346 28.654-64 64-64v-576c0-35.346 28.654-64 64-64h448c35.346 0 64 28.654 64 64V512c35.346 0 64 28.654 64 64v64C768 675.346 739.346 704 704 704zM640-32c0-17.673-14.327-32-32-32H224c-17.673 0-32 14.327-32 32V512h64v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480H384v-480c0-17.673 14.327-32 32-32s32 14.327 32 32l0.387 480h64L512 32c0-17.673 14.327-32 32-32s32 14.327 32 32V512h64V-32zM704 592c0-8.837-7.163-16-16-16H144c-8.837 0-16 7.163-16 16v32c0 8.837 7.163 16 16 16h544c8.837 0 16-7.163 16-16V592z" horiz-adv-x="768" />
+<glyph glyph-name="triangle-down" unicode="&#xf05b;" d="M0 448l383.75-383.75L767.5 448H0z" horiz-adv-x="767.5" />
+<glyph glyph-name="triangle-left" unicode="&#xf044;" d="M0 320.125l383.75-383.75v767.5L0 320.125z" horiz-adv-x="383.75" />
+<glyph glyph-name="triangle-right" unicode="&#xf05a;" d="M0.062 703.75L383.812 320 0.062-63.75V703.75z" horiz-adv-x="383.875" />
+<glyph glyph-name="triangle-up" unicode="&#xf0aa;" d="M383.75 576L0 192.25h767.5L383.75 576z" horiz-adv-x="767.5" />
+<glyph glyph-name="unfold" unicode="&#xf039;" d="M384 448h128V640h128L448 832 256 640h128V448zM576 576v-64h224L672 384H224L96 512h224v64H0v-63.999L160 352 0 192v-64h320v64H96l128 128h448l128-128H576v-64h320v64L736 352l160 160.001V576H576zM512 256H384v-192H256l192-192 192 192H512V256z" horiz-adv-x="896" />
+<glyph glyph-name="unmute" unicode="&#xf0ba;" d="M128 448H0v-256h128l256-192h64V640h-64L128 448zM538.51 410.51c-12.496 12.497-32.758 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 24.994-24.993 24.994-65.516 0-90.51-12.496-12.496-12.496-32.758 0-45.255 12.497-12.496 32.759-12.496 45.255 0C588.497 279.47900000000004 588.497 360.523 538.51 410.51zM629.02 501.019c-12.495 12.497-32.758 12.497-45.255 0-12.495-12.496-12.495-32.758 0-45.255 74.981-74.98 74.981-196.548 0-271.528-12.495-12.497-12.495-32.76 0-45.256 12.497-12.496 32.76-12.496 45.255 0C728.994 238.95399999999995 728.994 401.045 629.02 501.019zM719.529 591.529c-12.497 12.497-32.76 12.497-45.255 0-12.496-12.496-12.496-32.758 0-45.255 124.968-124.968 124.968-327.58 0-452.548-12.496-12.497-12.496-32.759 0-45.255 12.495-12.497 32.758-12.497 45.255 0C869.49 198.433 869.49 441.568 719.529 591.529z" horiz-adv-x="896" />
+<glyph glyph-name="versions" unicode="&#xf064;" d="M0 128h128v64H64V448h64v64H0V128zM384 640v-640h512V640H384zM768 128H512V512h256V128zM192 64h128v64h-64V512h64v64H192V64z" horiz-adv-x="896" />
+<glyph glyph-name="x" unicode="&#xf081;" d="M640 512L512 640 320 448 128 640 0 512l192-192L0 128l128-128 192 192 192-192 128 128L448 320 640 512z" horiz-adv-x="640" />
+<glyph glyph-name="zap" unicode="&#x26A1;" d="M640 384H384L576 832 0 256h256L64-192 640 384z" horiz-adv-x="640" />
+</font>
+</defs>
+</svg>
diff --git a/src/main/resources/octicons/octicons.ttf b/src/main/resources/octicons/octicons.ttf
new file mode 100644
index 0000000..189ca28
--- /dev/null
+++ b/src/main/resources/octicons/octicons.ttf
Binary files differ
diff --git a/src/main/resources/octicons/octicons.woff b/src/main/resources/octicons/octicons.woff
new file mode 100644
index 0000000..2b770e4
--- /dev/null
+++ b/src/main/resources/octicons/octicons.woff
Binary files differ
diff --git a/src/main/resources/octicons/sprockets-octicons.scss b/src/main/resources/octicons/sprockets-octicons.scss
new file mode 100644
index 0000000..0e518fc
--- /dev/null
+++ b/src/main/resources/octicons/sprockets-octicons.scss
@@ -0,0 +1,230 @@
+@font-face {
+  font-family: 'octicons';
+  src: font-url('octicons.eot?#iefix') format('embedded-opentype'),
+       font-url('octicons.woff') format('woff'),
+       font-url('octicons.ttf') format('truetype'),
+       font-url('octicons.svg#octicons') format('svg');
+  font-weight: normal;
+  font-style: normal;
+}
+
+// .octicon is optimized for 16px.
+// .mega-octicon is optimized for 32px but can be used larger.
+.octicon, .mega-octicon {
+  font: normal normal normal 16px/1 octicons;
+  display: inline-block;
+  text-decoration: none;
+  text-rendering: auto;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-user-select: none;
+  -moz-user-select: none;
+  -ms-user-select: none;
+  user-select: none;
+}
+.mega-octicon { font-size: 32px; }
+
+.octicon-alert:before { content: '\f02d'} /*  */
+.octicon-alignment-align:before { content: '\f08a'} /*  */
+.octicon-alignment-aligned-to:before { content: '\f08e'} /*  */
+.octicon-alignment-unalign:before { content: '\f08b'} /*  */
+.octicon-arrow-down:before { content: '\f03f'} /*  */
+.octicon-arrow-left:before { content: '\f040'} /*  */
+.octicon-arrow-right:before { content: '\f03e'} /*  */
+.octicon-arrow-small-down:before { content: '\f0a0'} /*  */
+.octicon-arrow-small-left:before { content: '\f0a1'} /*  */
+.octicon-arrow-small-right:before { content: '\f071'} /*  */
+.octicon-arrow-small-up:before { content: '\f09f'} /*  */
+.octicon-arrow-up:before { content: '\f03d'} /*  */
+.octicon-beer:before { content: '\f069'} /*  */
+.octicon-book:before { content: '\f007'} /*  */
+.octicon-bookmark:before { content: '\f07b'} /*  */
+.octicon-briefcase:before { content: '\f0d3'} /*  */
+.octicon-broadcast:before { content: '\f048'} /*  */
+.octicon-browser:before { content: '\f0c5'} /*  */
+.octicon-bug:before { content: '\f091'} /*  */
+.octicon-calendar:before { content: '\f068'} /*  */
+.octicon-check:before { content: '\f03a'} /*  */
+.octicon-checklist:before { content: '\f076'} /*  */
+.octicon-chevron-down:before { content: '\f0a3'} /*  */
+.octicon-chevron-left:before { content: '\f0a4'} /*  */
+.octicon-chevron-right:before { content: '\f078'} /*  */
+.octicon-chevron-up:before { content: '\f0a2'} /*  */
+.octicon-circle-slash:before { content: '\f084'} /*  */
+.octicon-circuit-board:before { content: '\f0d6'} /*  */
+.octicon-clippy:before { content: '\f035'} /*  */
+.octicon-clock:before { content: '\f046'} /*  */
+.octicon-cloud-download:before { content: '\f00b'} /*  */
+.octicon-cloud-upload:before { content: '\f00c'} /*  */
+.octicon-code:before { content: '\f05f'} /*  */
+.octicon-color-mode:before { content: '\f065'} /*  */
+.octicon-comment-add:before,
+.octicon-comment:before { content: '\f02b'} /*  */
+.octicon-comment-discussion:before { content: '\f04f'} /*  */
+.octicon-credit-card:before { content: '\f045'} /*  */
+.octicon-dash:before { content: '\f0ca'} /*  */
+.octicon-dashboard:before { content: '\f07d'} /*  */
+.octicon-database:before { content: '\f096'} /*  */
+.octicon-device-camera:before { content: '\f056'} /*  */
+.octicon-device-camera-video:before { content: '\f057'} /*  */
+.octicon-device-desktop:before { content: '\f27c'} /*  */
+.octicon-device-mobile:before { content: '\f038'} /*  */
+.octicon-diff:before { content: '\f04d'} /*  */
+.octicon-diff-added:before { content: '\f06b'} /*  */
+.octicon-diff-ignored:before { content: '\f099'} /*  */
+.octicon-diff-modified:before { content: '\f06d'} /*  */
+.octicon-diff-removed:before { content: '\f06c'} /*  */
+.octicon-diff-renamed:before { content: '\f06e'} /*  */
+.octicon-ellipsis:before { content: '\f09a'} /*  */
+.octicon-eye-unwatch:before,
+.octicon-eye-watch:before,
+.octicon-eye:before { content: '\f04e'} /*  */
+.octicon-file-binary:before { content: '\f094'} /*  */
+.octicon-file-code:before { content: '\f010'} /*  */
+.octicon-file-directory:before { content: '\f016'} /*  */
+.octicon-file-media:before { content: '\f012'} /*  */
+.octicon-file-pdf:before { content: '\f014'} /*  */
+.octicon-file-submodule:before { content: '\f017'} /*  */
+.octicon-file-symlink-directory:before { content: '\f0b1'} /*  */
+.octicon-file-symlink-file:before { content: '\f0b0'} /*  */
+.octicon-file-text:before { content: '\f011'} /*  */
+.octicon-file-zip:before { content: '\f013'} /*  */
+.octicon-flame:before { content: '\f0d2'} /*  */
+.octicon-fold:before { content: '\f0cc'} /*  */
+.octicon-gear:before { content: '\f02f'} /*  */
+.octicon-gift:before { content: '\f042'} /*  */
+.octicon-gist:before { content: '\f00e'} /*  */
+.octicon-gist-secret:before { content: '\f08c'} /*  */
+.octicon-git-branch-create:before,
+.octicon-git-branch-delete:before,
+.octicon-git-branch:before { content: '\f020'} /*  */
+.octicon-git-commit:before { content: '\f01f'} /*  */
+.octicon-git-compare:before { content: '\f0ac'} /*  */
+.octicon-git-merge:before { content: '\f023'} /*  */
+.octicon-git-pull-request-abandoned:before,
+.octicon-git-pull-request:before { content: '\f009'} /*  */
+.octicon-globe:before { content: '\f0b6'} /*  */
+.octicon-graph:before { content: '\f043'} /*  */
+.octicon-heart:before { content: '\2665'} /* ♥ */
+.octicon-history:before { content: '\f07e'} /*  */
+.octicon-home:before { content: '\f08d'} /*  */
+.octicon-horizontal-rule:before { content: '\f070'} /*  */
+.octicon-hourglass:before { content: '\f09e'} /*  */
+.octicon-hubot:before { content: '\f09d'} /*  */
+.octicon-inbox:before { content: '\f0cf'} /*  */
+.octicon-info:before { content: '\f059'} /*  */
+.octicon-issue-closed:before { content: '\f028'} /*  */
+.octicon-issue-opened:before { content: '\f026'} /*  */
+.octicon-issue-reopened:before { content: '\f027'} /*  */
+.octicon-jersey:before { content: '\f019'} /*  */
+.octicon-jump-down:before { content: '\f072'} /*  */
+.octicon-jump-left:before { content: '\f0a5'} /*  */
+.octicon-jump-right:before { content: '\f0a6'} /*  */
+.octicon-jump-up:before { content: '\f073'} /*  */
+.octicon-key:before { content: '\f049'} /*  */
+.octicon-keyboard:before { content: '\f00d'} /*  */
+.octicon-law:before { content: '\f0d8'} /*  */
+.octicon-light-bulb:before { content: '\f000'} /*  */
+.octicon-link:before { content: '\f05c'} /*  */
+.octicon-link-external:before { content: '\f07f'} /*  */
+.octicon-list-ordered:before { content: '\f062'} /*  */
+.octicon-list-unordered:before { content: '\f061'} /*  */
+.octicon-location:before { content: '\f060'} /*  */
+.octicon-gist-private:before,
+.octicon-mirror-private:before,
+.octicon-git-fork-private:before,
+.octicon-lock:before { content: '\f06a'} /*  */
+.octicon-logo-github:before { content: '\f092'} /*  */
+.octicon-mail:before { content: '\f03b'} /*  */
+.octicon-mail-read:before { content: '\f03c'} /*  */
+.octicon-mail-reply:before { content: '\f051'} /*  */
+.octicon-mark-github:before { content: '\f00a'} /*  */
+.octicon-markdown:before { content: '\f0c9'} /*  */
+.octicon-megaphone:before { content: '\f077'} /*  */
+.octicon-mention:before { content: '\f0be'} /*  */
+.octicon-microscope:before { content: '\f089'} /*  */
+.octicon-milestone:before { content: '\f075'} /*  */
+.octicon-mirror-public:before,
+.octicon-mirror:before { content: '\f024'} /*  */
+.octicon-mortar-board:before { content: '\f0d7'} /*  */
+.octicon-move-down:before { content: '\f0a8'} /*  */
+.octicon-move-left:before { content: '\f074'} /*  */
+.octicon-move-right:before { content: '\f0a9'} /*  */
+.octicon-move-up:before { content: '\f0a7'} /*  */
+.octicon-mute:before { content: '\f080'} /*  */
+.octicon-no-newline:before { content: '\f09c'} /*  */
+.octicon-octoface:before { content: '\f008'} /*  */
+.octicon-organization:before { content: '\f037'} /*  */
+.octicon-package:before { content: '\f0c4'} /*  */
+.octicon-paintcan:before { content: '\f0d1'} /*  */
+.octicon-pencil:before { content: '\f058'} /*  */
+.octicon-person-add:before,
+.octicon-person-follow:before,
+.octicon-person:before { content: '\f018'} /*  */
+.octicon-pin:before { content: '\f041'} /*  */
+.octicon-playback-fast-forward:before { content: '\f0bd'} /*  */
+.octicon-playback-pause:before { content: '\f0bb'} /*  */
+.octicon-playback-play:before { content: '\f0bf'} /*  */
+.octicon-playback-rewind:before { content: '\f0bc'} /*  */
+.octicon-plug:before { content: '\f0d4'} /*  */
+.octicon-repo-create:before,
+.octicon-gist-new:before,
+.octicon-file-directory-create:before,
+.octicon-file-add:before,
+.octicon-plus:before { content: '\f05d'} /*  */
+.octicon-podium:before { content: '\f0af'} /*  */
+.octicon-primitive-dot:before { content: '\f052'} /*  */
+.octicon-primitive-square:before { content: '\f053'} /*  */
+.octicon-pulse:before { content: '\f085'} /*  */
+.octicon-puzzle:before { content: '\f0c0'} /*  */
+.octicon-question:before { content: '\f02c'} /*  */
+.octicon-quote:before { content: '\f063'} /*  */
+.octicon-radio-tower:before { content: '\f030'} /*  */
+.octicon-repo-delete:before,
+.octicon-repo:before { content: '\f001'} /*  */
+.octicon-repo-clone:before { content: '\f04c'} /*  */
+.octicon-repo-force-push:before { content: '\f04a'} /*  */
+.octicon-gist-fork:before,
+.octicon-repo-forked:before { content: '\f002'} /*  */
+.octicon-repo-pull:before { content: '\f006'} /*  */
+.octicon-repo-push:before { content: '\f005'} /*  */
+.octicon-rocket:before { content: '\f033'} /*  */
+.octicon-rss:before { content: '\f034'} /*  */
+.octicon-ruby:before { content: '\f047'} /*  */
+.octicon-screen-full:before { content: '\f066'} /*  */
+.octicon-screen-normal:before { content: '\f067'} /*  */
+.octicon-search-save:before,
+.octicon-search:before { content: '\f02e'} /*  */
+.octicon-server:before { content: '\f097'} /*  */
+.octicon-settings:before { content: '\f07c'} /*  */
+.octicon-log-in:before,
+.octicon-sign-in:before { content: '\f036'} /*  */
+.octicon-log-out:before,
+.octicon-sign-out:before { content: '\f032'} /*  */
+.octicon-split:before { content: '\f0c6'} /*  */
+.octicon-squirrel:before { content: '\f0b2'} /*  */
+.octicon-star-add:before,
+.octicon-star-delete:before,
+.octicon-star:before { content: '\f02a'} /*  */
+.octicon-steps:before { content: '\f0c7'} /*  */
+.octicon-stop:before { content: '\f08f'} /*  */
+.octicon-repo-sync:before,
+.octicon-sync:before { content: '\f087'} /*  */
+.octicon-tag-remove:before,
+.octicon-tag-add:before,
+.octicon-tag:before { content: '\f015'} /*  */
+.octicon-telescope:before { content: '\f088'} /*  */
+.octicon-terminal:before { content: '\f0c8'} /*  */
+.octicon-three-bars:before { content: '\f05e'} /*  */
+.octicon-tools:before { content: '\f031'} /*  */
+.octicon-trashcan:before { content: '\f0d0'} /*  */
+.octicon-triangle-down:before { content: '\f05b'} /*  */
+.octicon-triangle-left:before { content: '\f044'} /*  */
+.octicon-triangle-right:before { content: '\f05a'} /*  */
+.octicon-triangle-up:before { content: '\f0aa'} /*  */
+.octicon-unfold:before { content: '\f039'} /*  */
+.octicon-unmute:before { content: '\f0ba'} /*  */
+.octicon-versions:before { content: '\f064'} /*  */
+.octicon-remove-close:before,
+.octicon-x:before { content: '\f081'} /*  */
+.octicon-zap:before { content: '\26A1'} /* ⚡ */
diff --git a/src/main/resources/sub32.png b/src/main/resources/sub32.png
new file mode 100644
index 0000000..ebcfe13
--- /dev/null
+++ b/src/main/resources/sub32.png
Binary files differ
diff --git a/src/site/design.mkd b/src/site/design.mkd
index cd4b1b7..9ef302c 100644
--- a/src/site/design.mkd
+++ b/src/site/design.mkd
@@ -57,6 +57,7 @@
 - [jedis](https://github.com/xetorthio/jedis) (MIT)
 - [Mina SSHD](https://mina.apache.org) (Apache 2.0)
 - [pf4j](https://github.com/decebals/pf4j) (Apache 2.0)
+- [google-guice](https://code.google.com/p/google-guice) (Apache 2.0)
 
 ### Other Build Dependencies
 - [Fancybox image viewer](http://fancybox.net) (MIT and GPL dual-licensed)
diff --git a/src/site/plugins_extensions.mkd b/src/site/plugins_extensions.mkd
index 9e0d170..82dd30b 100644
--- a/src/site/plugins_extensions.mkd
+++ b/src/site/plugins_extensions.mkd
@@ -338,6 +338,16 @@
     }
 
     @Override
+    public void onFork(RepositoryModel origin, RepositoryModel fork) {
+    	log.info("{} forked to {}", origin, fork);
+    }
+
+    @Override
+    public void onRename(String oldName, RepositoryModel repo) {
+    	log.info("{} renamed to {}", oldName, repo);
+    }
+
+    @Override
     public void onDeletion(RepositoryModel repo) {
     	log.info("Gitblit deleted {}", repo);
     }
diff --git a/src/site/roadmap.mkd b/src/site/roadmap.mkd
deleted file mode 100644
index ea64321..0000000
--- a/src/site/roadmap.mkd
+++ /dev/null
@@ -1,8 +0,0 @@
-## Roadmap
-
-This is not exactly a formal roadmap but it is a priority list of what might be implemented in future releases.  
-This list is volatile and may not reflect what will be in the next release.
-
-* Add support for Project owners/administrators (ticket-75)
-* Add Project create/update pages
-* Integrate improvements for git-flow (ticket-55)
diff --git a/src/site/rpc.mkd b/src/site/rpc.mkd
index de4ed47..4b065bf 100644
--- a/src/site/rpc.mkd
+++ b/src/site/rpc.mkd
@@ -131,7 +131,7 @@
 
 ### Example: LIST_REPOSITORIES
 
-**url**: https://localhost/rpc?req=LIST_REPOSITORIES  
+**url**: https://localhost/rpc/?req=LIST_REPOSITORIES  
 **response body**: Map&lt;String, RepositoryModel&gt; where the map key is the clone url of the repository
 
 ```json
@@ -183,7 +183,7 @@
 
 The original repository name is specified in the *name* url parameter.  The new name is set within the JSON object.
 
-**url**: https://localhost/rpc?req=EDIT_REPOSITORY&name=libraries/xmlapache.git  
+**url**: https://localhost/rpc/?req=EDIT_REPOSITORY&name=libraries/xmlapache.git  
 **post body**: RepositoryModel
 
 ```json
@@ -211,7 +211,7 @@
 ```
 
 ### Example: LIST_USERS
-**url**: https://localhost/rpc?req=LIST_USERS  
+**url**: https://localhost/rpc/?req=LIST_USERS  
 **response body**: List&lt;UserModel&gt;
 
 ```json
@@ -237,7 +237,7 @@
 ```
 
 ### Example: LIST_SETTINGS
-**url**: https://localhost/rpc?req=LIST_SETTINGS  
+**url**: https://localhost/rpc/?req=LIST_SETTINGS  
 **response body**: ServerSettings
 
 ```json
@@ -268,7 +268,7 @@
 ```
 
 ### Example: LIST_STATUS
-**url**: https://localhost/rpc?req=LIST_STATUS  
+**url**: https://localhost/rpc/?req=LIST_STATUS  
 **response body**: ServerStatus
 
 ```json
diff --git a/src/site/setup_authentication.mkd b/src/site/setup_authentication.mkd
index 87b6749..a3bf445 100644
--- a/src/site/setup_authentication.mkd
+++ b/src/site/setup_authentication.mkd
@@ -83,10 +83,16 @@
 
 ### PAM Authentication
 
-PAM authentication is based on the use of libpam4j and JNA.  To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine and the user that Gitblit runs-as must have root permissions.
+PAM authentication is based on the use of libpam4j and JNA.  To use this service, your Gitblit server must be installed on a Linux/Unix/MacOSX machine.
 
     realm.authenticationProviders = pam
-    realm.pam.serviceName = system-auth
+    realm.pam.serviceName = gitblit
+    
+Then define a gitblit authentication policy in `/etc/pam.d/gitblit`
+
+    # PAM configuration for the gitblit service
+    # Standard Un*x authentication.
+    @include common-auth
 
 ### Htpasswd Authentication
 
diff --git a/src/site/setup_bugtraq.mkd b/src/site/setup_bugtraq.mkd
index ad4a75d..fcd45d4 100644
--- a/src/site/setup_bugtraq.mkd
+++ b/src/site/setup_bugtraq.mkd
@@ -24,7 +24,7 @@
         logregex = "Change-Id:\\s*(I[A-Fa-f0-9]{40})"
     
     [bugtraq "jira"]
-        https://jira.atlassian.com/browse/%BUGID%
+        url = https://jira.atlassian.com/browse/%BUGID%
         logregex = (JRA-\\d+)
 
     [bugtraq "github"]
diff --git a/src/site/setup_fail2ban.mkd b/src/site/setup_fail2ban.mkd
new file mode 100644
index 0000000..c735968
--- /dev/null
+++ b/src/site/setup_fail2ban.mkd
@@ -0,0 +1,24 @@
+## Configure fail2ban for Gitblit-SSH
+
+This procedure uses [fail2ban](http://www.fail2ban.org/).
+
+First, create a new filter file `gitblit.conf` in filter directory (Debian/CentOS: `/etc/fail2ban/filter.d/`) or into `filter.conf` file. Here is an example:
+
+    [Definition]
+    failregex =  Failed login attempt for .+, invalid credentials from <HOST>\s*$
+                 could not authenticate .*? \(/<HOST>:[0-9]*\) for SSH using the supplied password$
+    ignoreregex =
+
+Then edit `jail.conf` to add "gitblit" service (Debian: `/etc/fail2ban/jail.conf`). For example:
+
+    [gitblit]
+    enabled = true
+    port = 443,29418
+    protocol = tcp
+    filter = gitblit
+    logpath = /var/log/gitblit.log
+
+
+Reload fail2ban config to apply (`fail2ban-client reload`).
+
+Check the status of the gitblit fail2ban jail with `fail2ban-client status gitblit`
diff --git a/src/site/setup_go.mkd b/src/site/setup_go.mkd
index 8117244..c46e04b 100644
--- a/src/site/setup_go.mkd
+++ b/src/site/setup_go.mkd
@@ -1,7 +1,7 @@
 ## Gitblit GO Installation & Setup
 
 1. Download and unzip Gitblit GO [${project.releaseVersion} (Windows)](%GCURL%gitblit-${project.releaseVersion}.zip) or [${project.releaseVersion} (Linux/OSX)](%GCURL%gitblit-${project.releaseVersion}.tar.gz).  
-*Its best to eliminate spaces in the path name.* 
+*It is best to eliminate spaces in the path name.* 
 2. The server itself is configured through a simple text file.
 Open `data/gitblit.properties` in your favorite text editor and make sure to review and set:
     - *server.httpPort* and *server.httpsPort*
@@ -9,7 +9,7 @@
     **https** is strongly recommended because passwords are insecurely transmitted form your browser/git client using Basic authentication!
     - *git.packedGitLimit* (set larger than the size of your largest repository)
 3. Execute `authority.cmd` or `java -cp gitblit.jar com.gitblit.authority.Launcher --baseFolder data` from a command-line
-**NOTE:** The Authority is a Swing GUI application.  Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated certificates.  Skipping this step will result in certificates with default metadata.
+**NOTE:** The Authority is a Swing GUI application.  Use of this tool is not required as Gitblit GO will startup and create SSL certificates itself, BUT use of this tool allows you to control the identification metadata used in the generated self-signed certificates.  Skipping this step will result in certificates with default metadata.
     1. fill out the fields in the *new certificate defaults* dialog
 	2. enter the store password used in *server.storePassword* when prompted.  This generates an SSL certificate for **localhost**.
 	3. you may want to generate an SSL certificate for the hostname or ip address hostnames you are serving from
@@ -30,6 +30,11 @@
 
 You can specify `GITBLIT_HOME` either as an environment variable or as a `-DGITBLIT_HOME` JVM system property.
 
+### Including Other Properties
+SINCE 1.7.0
+
+Gitblit supports loading it's settings from multiple properties files.  You can achieve this using the `include=filename` key.  This setting supports loading multiple files using a *comma* as the delimiter.  They are processed in the order defined and they may be nested (i.e. your included properties may include properties, etc, etc).
+
 ### Creating your own Self-Signed SSL Certificate
 Gitblit GO (and Gitblit Certificate Authority) automatically generates a Certificate Authority (CA) certificate and an ssl certificate signed by this CA certificate that is bound to *localhost*.
 
diff --git a/src/site/setup_war.mkd b/src/site/setup_war.mkd
index a060bae..21492b2 100644
--- a/src/site/setup_war.mkd
+++ b/src/site/setup_war.mkd
@@ -31,3 +31,8 @@
 
 1. Open TOMCAT_HOME/conf/context.xml
 2. Insert an *Environment* node within the *Context* node.<pre>&lt;Environment name="baseFolder" type="java.lang.String" value="c:/projects/git/gitblit/data" override="false" /&gt;</pre>
+
+### Including Other Properties
+SINCE 1.7.0
+
+Gitblit supports loading it's settings from multiple properties files.  You can achieve this using the `include=filename` key.  This setting supports loading multiple files using a *comma* as the delimiter.  They are processed in the order defined and they may be nested (i.e. your included properties may include properties, etc, etc).
diff --git a/src/site/siteindex.mkd b/src/site/siteindex.mkd
index ae954bf..aec5c42 100644
--- a/src/site/siteindex.mkd
+++ b/src/site/siteindex.mkd
@@ -23,12 +23,9 @@
 		<tbody>
 		<tr><th>License</th><td><a href="http://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</a></td></tr>
 		<tr><th>Sources</th><td><a href="${project.scmUrl}">GitHub</a></td></tr>		
-		<tr><th>Issues</th><td><a href="${project.issuesUrl}">GoogleCode</a></td></tr>
+		<tr><th>Issues</th><td><a href="${project.issuesUrl}">GitHub</a></td></tr>
 		<tr><th>Discussion</th><td><a href="${project.forumUrl}">Gitblit Group</a></td></tr>
-		<tr><th>Google+</th><td><a href="${project.socialNetworkUrl}">Gitblit+</a></td></tr>
 		<tr><th>Ohloh</th><td><a target="_top" href="http://www.ohloh.net/p/gitblit"><img border="0" width="100" height="16" src="http://www.ohloh.net/p/gitblit/widgets/project_thin_badge.gif" alt="Ohloh project report for Gitblit" /></a></td></tr>
-		<tr><th>Donations</th><td>If you enjoy Gitblit and want to support its development, please consider making a donation to <a href="http://www.stjude.org">St. Jude Children's Research Hospital</a>.
-		<a href="http://www.stjude.org" alt="St. Jude Children's Research Hospital"><img style="padding-top:10px;" src="stjude_150x150.gif"/></a></td></tr>
 		</tbody>
 		</table>
 	</div>
diff --git a/src/site/tickets_overview.mkd b/src/site/tickets_overview.mkd
index 14e4ab9..10d0e18 100644
--- a/src/site/tickets_overview.mkd
+++ b/src/site/tickets_overview.mkd
@@ -70,7 +70,7 @@
 1. The organizational unit of the Gitblit Tickets feature is the *ticket*.
 2. A *ticket* can be used to report a bug, request an enhancement, ask a question, etc.  A ticket can also be used to collaborate on a *patchset* that addresses the request.
 3. A *patchset* is a series of commits from a merge base that exists in the target branch of your repository to the tip of the patchset.  A patchset may only contain a single commit, or it may contain dozens.  This is similar to the commits in a *Pull Request*.  One important distinction here is that in Gitblit, each *Patchset* is developed on a separate branch and can be completely rewritten without losing the previous patchsets (this creates a new patchset).
-4. A *ticket* monitors the development of *patchsets* by tracking *revisions* to *patchsets*.  The ticket alslo monitors rewritten patchsets. Each *patchset* is developed on it's own Git branch.
+4. A *ticket* monitors the development of *patchsets* by tracking *revisions* to *patchsets*.  The ticket also monitors rewritten patchsets. Each *patchset* is developed on it's own Git branch.
  
 Tracking *patchsets* is similar in concept to Gerrit, but there is a critical difference.  In Gerrit, *every* commit in the *patchset* has it's own ticket  **AND** Git branch.  In Gerrit, *patchsets* can be easily rewritten and for each rewritten commit, a new branch ref is created.  This leads to an explosion in refs for the repository over time.  In Gitblit, only the tip of the *patchset* gets a branch ref and this branch ref is updated, like a regular branch, unless a rewrite is detected.
 
diff --git a/src/site/upgrade_go.mkd b/src/site/upgrade_go.mkd
index 54d7ab5..a009258 100644
--- a/src/site/upgrade_go.mkd
+++ b/src/site/upgrade_go.mkd
@@ -1,3 +1,17 @@
+## Upgrading Gitblit GO (1.7.0+)
+
+The default `gitblit.properties` file has been split into two files: `gitblit.properties`, which is the recommended file for setting your configuration, and `defaults.properties` which are Gitblit's default settings.
+
+    # Include Gitblit's 'defaults.properties' within your configuration.
+    #
+    # COMMA-DELIMITED
+    # SINCE 1.7.0
+    include = defaults.properties
+
+Notice that the default settings are *included* by your `gitblit.properties` file.  The disadvantage to this approach is you must flip between discovering/reading the settings in `defaults.properties` and setting them in `gitblit.properties`, but there are some clear advantages too.  This setup is not required.  You may continue to keep all your settings in `gitblit.properties` like before.
+
+Additionally you may find it useful if you are maintaining several Gitblit instances to share common properties files.
+
 ## Upgrading Gitblit GO (1.2.1+)
  
 1. Unzip Gitblit GO to a new folder
diff --git a/src/site/upgrade_war.mkd b/src/site/upgrade_war.mkd
index 34ffd75..4165ca1 100644
--- a/src/site/upgrade_war.mkd
+++ b/src/site/upgrade_war.mkd
@@ -1,3 +1,21 @@
+## Upgrading Gitblit WAR (1.7.0+)
+
+The default `gitblit.properties` file has been split into two files: `gitblit.properties`, which is the recommended file for setting your configuration, and `defaults.properties` which are Gitblit's default settings.
+
+    # Include Gitblit's 'defaults.properties' within your configuration.
+    #
+    # COMMA-DELIMITED
+    # SINCE 1.7.0
+    include = defaults.properties
+
+Notice that the default settings are *included* by your `gitblit.properties` file.  The disadvantage to this approach is you must flip between discovering/reading the settings in `defaults.properties` and setting them in `gitblit.properties`, but there are some clear advantages too.  This setup is not required.  You may continue to keep all your settings in `gitblit.properties` like before.
+
+Additionally you may find it useful if you are maintaining several Gitblit instances to share common properties files.
+
+## Upgrading Gitblit WAR (1.4.0+)
+
+The *baseFolder* context parameter has been replaced with a *baseFolder* JNDI env-entry.  This means you can define the *baseFolder* from the administrative console of your servlet container and not have to manipulate anything in the web.xml file.
+
 ## Upgrading Gitblit WAR (1.2.1+)
 1. Make sure your `WEB-INF/web.xml` *baseFolder* context parameter is not `${contextFolder}/WEB-INF/data`!
 If it is, move your `WEB-INF/data` folder to a location writeable by your servlet container.
@@ -5,7 +23,6 @@
 3. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
 4. Review and optionally apply any new settings as indicated in the [release log](releases.html) to `${baseFolder}/gitblit.properties`. 
  
-
 ## Upgrading Gitblit WAR (pre-1.2.1)
 
 1. Create a `data` as outlined in step 1 of *Upgrading Gitblit GO (pre-1.2.1)*
@@ -14,7 +31,3 @@
 4. Copy the new WAR's `WEB-INF/data/gitblit.properties` file to your data folder
 5. Manually apply any changes you made to your original web.xml file to the gitblit.properties file you copied to your data folder
 6. Edit the new WAR's `WEB-INF/web.xml` file and set the *baseFolder* context parameter to your external baseFolder.
-
-## Upgrading Gitblit WAR (1.4.0+)
-
-The *baseFolder* context parameter has been replaced with a *baseFolder* JNDI env-entry.  This means you can define the *baseFolder* from the administrative console of your servlet container and not have to manipulate anything in the web.xml file.
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
index 0cdee6c..f8dc888 100644
--- a/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
+++ b/src/test/java/com/gitblit/tests/AuthenticationManagerTest.java
@@ -15,15 +15,44 @@
  */
 package com.gitblit.tests;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.Principal;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.AsyncContext;
+import javax.servlet.DispatcherType;
+import javax.servlet.RequestDispatcher;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletInputStream;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionContext;
+import javax.servlet.http.HttpUpgradeHandler;
+import javax.servlet.http.Part;
 
 import org.junit.Test;
 
+import com.gitblit.IUserService;
+import com.gitblit.Keys;
 import com.gitblit.manager.AuthenticationManager;
 import com.gitblit.manager.IAuthenticationManager;
-import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.IRuntimeManager;
 import com.gitblit.manager.RuntimeManager;
 import com.gitblit.manager.UserManager;
+import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.tests.mock.MemorySettings;
 import com.gitblit.utils.XssFilter;
@@ -35,35 +64,647 @@
  * @author James Moger
  *
  */
+@SuppressWarnings("deprecation")
 public class AuthenticationManagerTest extends GitblitUnitTest {
 
-	IUserManager users;
+	UserManager users;
 
-    MemorySettings getSettings() {
-    	return new MemorySettings(new HashMap<String, Object>());
-    }
+	private static final class DummyHttpServletRequest implements HttpServletRequest {
 
-    IAuthenticationManager newAuthenticationManager() {
-    	XssFilter xssFilter = new AllowXssFilter();
-    	RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start();
-    	users = new UserManager(runtime, null).start();
-    	AuthenticationManager auth = new AuthenticationManager(runtime, users).start();
-    	return auth;
-    }
+		@Override
+		public Object getAttribute(String name) {
+			return null;
+		}
 
-    @Test
-    public void testAuthenticate() throws Exception {
-    	IAuthenticationManager auth = newAuthenticationManager();
+		@Override
+		public Enumeration<String> getAttributeNames() {
+			return null;
+		}
 
-    	UserModel user = new UserModel("sunnyjim");
+		@Override
+		public String getCharacterEncoding() {
+			return null;
+		}
+
+		@Override
+		public void setCharacterEncoding(String env)
+				throws UnsupportedEncodingException {
+		}
+
+		@Override
+		public int getContentLength() {
+			return 0;
+		}
+
+		@Override
+		public long getContentLengthLong() {
+			return 0;
+		}
+
+		@Override
+		public String getContentType() {
+			return null;
+		}
+
+		@Override
+		public ServletInputStream getInputStream() throws IOException {
+			return null;
+		}
+
+		@Override
+		public String getParameter(String name) {
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getParameterNames() {
+			return null;
+		}
+
+		@Override
+		public String[] getParameterValues(String name) {
+			return null;
+		}
+
+		@Override
+		public Map<String, String[]> getParameterMap() {
+			return null;
+		}
+
+		@Override
+		public String getProtocol() {
+			return null;
+		}
+
+		@Override
+		public String getScheme() {
+			return null;
+		}
+
+		@Override
+		public String getServerName() {
+			return null;
+		}
+
+		@Override
+		public int getServerPort() {
+			return 0;
+		}
+
+		@Override
+		public BufferedReader getReader() throws IOException {
+			return null;
+		}
+
+		@Override
+		public String getRemoteAddr() {
+			return null;
+		}
+
+		@Override
+		public String getRemoteHost() {
+			return null;
+		}
+
+		@Override
+		public void setAttribute(String name, Object o) {
+		}
+
+		@Override
+		public void removeAttribute(String name) {
+		}
+
+		@Override
+		public Locale getLocale() {
+			return null;
+		}
+
+		@Override
+		public Enumeration<Locale> getLocales() {
+			return null;
+		}
+
+		@Override
+		public boolean isSecure() {
+			return false;
+		}
+
+		@Override
+		public RequestDispatcher getRequestDispatcher(String path) {
+			return null;
+		}
+
+		@Override
+		public String getRealPath(String path) {
+			return null;
+		}
+
+		@Override
+		public int getRemotePort() {
+			return 0;
+		}
+
+		@Override
+		public String getLocalName() {
+			return null;
+		}
+
+		@Override
+		public String getLocalAddr() {
+			return null;
+		}
+
+		@Override
+		public int getLocalPort() {
+			return 0;
+		}
+
+		@Override
+		public ServletContext getServletContext() {
+			return null;
+		}
+
+		@Override
+		public AsyncContext startAsync() throws IllegalStateException {
+			return null;
+		}
+
+		@Override
+		public AsyncContext startAsync(ServletRequest servletRequest,
+				ServletResponse servletResponse)
+						throws IllegalStateException {
+			return null;
+		}
+
+		@Override
+		public boolean isAsyncStarted() {
+			return false;
+		}
+
+		@Override
+		public boolean isAsyncSupported() {
+			return false;
+		}
+
+		@Override
+		public AsyncContext getAsyncContext() {
+			return null;
+		}
+
+		@Override
+		public DispatcherType getDispatcherType() {
+			return null;
+		}
+
+		@Override
+		public String getAuthType() {
+			return null;
+		}
+
+		@Override
+		public Cookie[] getCookies() {
+			return null;
+		}
+
+		@Override
+		public long getDateHeader(String name) {
+			return 0;
+		}
+
+		@Override
+		public String getHeader(String name) {
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getHeaders(String name) {
+			return null;
+		}
+
+		@Override
+		public Enumeration<String> getHeaderNames() {
+			return null;
+		}
+
+		@Override
+		public int getIntHeader(String name) {
+			return 0;
+		}
+
+		@Override
+		public String getMethod() {
+			return null;
+		}
+
+		@Override
+		public String getPathInfo() {
+			return null;
+		}
+
+		@Override
+		public String getPathTranslated() {
+			return null;
+		}
+
+		@Override
+		public String getContextPath() {
+			return null;
+		}
+
+		@Override
+		public String getQueryString() {
+			return null;
+		}
+
+		@Override
+		public String getRemoteUser() {
+			return null;
+		}
+
+		@Override
+		public boolean isUserInRole(String role) {
+			if(role != null && "admin".equals(role)) {
+				return true;
+			}
+			return false;
+		}
+
+		@Override
+		public Principal getUserPrincipal() {
+			return new Principal(){
+				@Override
+				public String getName() {
+					return "sunnyjim";
+				}
+
+			};
+		}
+
+		@Override
+		public String getRequestedSessionId() {
+			return null;
+		}
+
+		@Override
+		public String getRequestURI() {
+			return null;
+		}
+
+		@Override
+		public StringBuffer getRequestURL() {
+			return null;
+		}
+
+		@Override
+		public String getServletPath() {
+			return null;
+		}
+
+		@Override
+		public HttpSession getSession(boolean create) {
+			return null;
+		}
+
+		final Map<String, Object> sessionAttributes = new HashMap<String, Object>();
+		@Override
+		public HttpSession getSession() {
+			return new HttpSession() {
+
+				@Override
+				public long getCreationTime() {
+					return 0;
+				}
+
+				@Override
+				public String getId() {
+					return null;
+				}
+
+				@Override
+				public long getLastAccessedTime() {
+					return 0;
+				}
+
+				@Override
+				public ServletContext getServletContext() {
+					return null;
+				}
+
+				@Override
+				public void setMaxInactiveInterval(int interval) {
+				}
+
+				@Override
+				public int getMaxInactiveInterval() {
+					return 0;
+				}
+
+				@Override
+				public HttpSessionContext getSessionContext() {
+					return null;
+				}
+
+				@Override
+				public Object getAttribute(String name) {
+					return sessionAttributes.get(name);
+				}
+
+				@Override
+				public Object getValue(String name) {
+					return null;
+				}
+
+				@Override
+				public Enumeration<String> getAttributeNames() {
+					return Collections.enumeration(sessionAttributes.keySet());
+				}
+
+				@Override
+				public String[] getValueNames() {
+					return null;
+				}
+
+				@Override
+				public void setAttribute(String name,
+						Object value) {
+				}
+
+				@Override
+				public void putValue(String name, Object value) {
+				}
+
+				@Override
+				public void removeAttribute(String name) {
+				}
+
+				@Override
+				public void removeValue(String name) {
+				}
+
+				@Override
+				public void invalidate() {
+				}
+
+				@Override
+				public boolean isNew() {
+					return false;
+				}
+
+			};
+		}
+
+		@Override
+		public String changeSessionId() {
+			return null;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdValid() {
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromCookie() {
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromURL() {
+			return false;
+		}
+
+		@Override
+		public boolean isRequestedSessionIdFromUrl() {
+			return false;
+		}
+
+		@Override
+		public boolean authenticate(HttpServletResponse response)
+				throws IOException, ServletException {
+			return false;
+		}
+
+		@Override
+		public void login(String username, String password)
+				throws ServletException {
+		}
+
+		@Override
+		public void logout() throws ServletException {
+		}
+
+		@Override
+		public Collection<Part> getParts() throws IOException,
+		ServletException {
+			return null;
+		}
+
+		@Override
+		public Part getPart(String name) throws IOException,
+		ServletException {
+			return null;
+		}
+
+		@Override
+		public <T extends HttpUpgradeHandler> T upgrade(
+				Class<T> handlerClass) throws IOException,
+				ServletException {
+			return null;
+		}
+
+	}
+
+	HashMap<String, Object> settings = new HashMap<String, Object>();
+
+	MemorySettings getSettings() {
+		return new MemorySettings(settings);
+	}
+
+	IAuthenticationManager newAuthenticationManager() {
+		XssFilter xssFilter = new AllowXssFilter();
+		RuntimeManager runtime = new RuntimeManager(getSettings(), xssFilter, GitBlitSuite.BASEFOLDER).start();
+		users = new UserManager(runtime, null).start();
+		final Map<String, UserModel> virtualUsers = new HashMap<String, UserModel>();
+		users.setUserService(new IUserService() {
+
+			@Override
+			public void setup(IRuntimeManager runtimeManager) {
+			}
+
+			@Override
+			public String getCookie(UserModel model) {
+				return null;
+			}
+
+			@Override
+			public UserModel getUserModel(char[] cookie) {
+				return null;
+			}
+
+			@Override
+			public UserModel getUserModel(String username) {
+				return virtualUsers.get(username);
+			}
+
+			@Override
+			public boolean updateUserModel(UserModel model) {
+				virtualUsers.put(model.username, model);
+				return true;
+			}
+
+			@Override
+			public boolean updateUserModels(Collection<UserModel> models) {
+				return false;
+			}
+
+			@Override
+			public boolean updateUserModel(String username, UserModel model) {
+				virtualUsers.put(username, model);
+				return true;
+			}
+
+			@Override
+			public boolean deleteUserModel(UserModel model) {
+				return false;
+			}
+
+			@Override
+			public boolean deleteUser(String username) {
+				return false;
+			}
+
+			@Override
+			public List<String> getAllUsernames() {
+				return null;
+			}
+
+			@Override
+			public List<UserModel> getAllUsers() {
+				return null;
+			}
+
+			@Override
+			public List<String> getAllTeamNames() {
+				return null;
+			}
+
+			@Override
+			public List<TeamModel> getAllTeams() {
+				return null;
+			}
+
+			@Override
+			public List<String> getTeamNamesForRepositoryRole(String role) {
+				return null;
+			}
+
+			@Override
+			public TeamModel getTeamModel(String teamname) {
+				return null;
+			}
+
+			@Override
+			public boolean updateTeamModel(TeamModel model) {
+				return false;
+			}
+
+			@Override
+			public boolean updateTeamModels(Collection<TeamModel> models) {
+				return false;
+			}
+
+			@Override
+			public boolean updateTeamModel(String teamname, TeamModel model) {
+				return false;
+			}
+
+			@Override
+			public boolean deleteTeamModel(TeamModel model) {
+				return false;
+			}
+
+			@Override
+			public boolean deleteTeam(String teamname) {
+				return false;
+			}
+
+			@Override
+			public List<String> getUsernamesForRepositoryRole(String role) {
+				return null;
+			}
+
+			@Override
+			public boolean renameRepositoryRole(String oldRole,
+					String newRole) {
+				return false;
+			}
+
+			@Override
+			public boolean deleteRepositoryRole(String role) {
+				return false;
+			}
+
+		});
+		AuthenticationManager auth = new AuthenticationManager(runtime, users).start();
+		return auth;
+	}
+
+	@Test
+	public void testAuthenticate() throws Exception {
+		IAuthenticationManager auth = newAuthenticationManager();
+
+		UserModel user = new UserModel("sunnyjim");
 		user.password = "password";
 		users.updateUserModel(user);
 
-		assertNotNull(auth.authenticate(user.username, user.password.toCharArray()));
+		assertNotNull(auth.authenticate(user.username, user.password.toCharArray(), null));
 		user.disabled = true;
 
 		users.updateUserModel(user);
-		assertNull(auth.authenticate(user.username, user.password.toCharArray()));
+		assertNull(auth.authenticate(user.username, user.password.toCharArray(), null));
 		users.deleteUserModel(user);
-    }
+	}
+
+	@Test
+	public void testContenairAuthenticate() throws Exception {
+		settings.put(Keys.realm.container.autoCreateAccounts, "true");
+		settings.put(Keys.realm.container.autoAccounts.displayName, "displayName");
+		settings.put(Keys.realm.container.autoAccounts.emailAddress, "emailAddress");
+		settings.put(Keys.realm.container.autoAccounts.adminRole, "admin");
+		settings.put(Keys.realm.container.autoAccounts.locale, "locale");
+
+		DummyHttpServletRequest request = new DummyHttpServletRequest();
+		request.sessionAttributes.put("displayName", "Sunny Jim");
+		request.sessionAttributes.put("emailAddress", "Jim.Sunny@gitblit.com");
+		request.sessionAttributes.put("locale", "it");
+
+		IAuthenticationManager auth = newAuthenticationManager();
+
+		UserModel user = auth.authenticate(request);
+
+		assertTrue(user.canAdmin);
+		assertEquals("Sunny Jim", user.displayName);
+		assertEquals("Jim.Sunny@gitblit.com", user.emailAddress);
+		assertEquals(Locale.ITALIAN, user.getPreferences().getLocale());
+	}
+
+	@Test
+	public void testContenairAuthenticateEmpty() throws Exception {
+		settings.put(Keys.realm.container.autoCreateAccounts, "true");
+		settings.put(Keys.realm.container.autoAccounts.displayName, "displayName");
+		settings.put(Keys.realm.container.autoAccounts.emailAddress, "emailAddress");
+		settings.put(Keys.realm.container.autoAccounts.adminRole, "notAdmin");
+
+		DummyHttpServletRequest request = new DummyHttpServletRequest();
+
+		IAuthenticationManager auth = newAuthenticationManager();
+
+		UserModel user = auth.authenticate(request);
+
+		assertFalse(user.canAdmin);
+		assertEquals("sunnyjim", user.displayName);
+		assertNull(user.emailAddress);
+		assertNull(user.getPreferences().getLocale());
+	}
+
 }
diff --git a/src/test/java/com/gitblit/tests/DiffUtilsTest.java b/src/test/java/com/gitblit/tests/DiffUtilsTest.java
index 9d627b8..e8e839a 100644
--- a/src/test/java/com/gitblit/tests/DiffUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/DiffUtilsTest.java
@@ -23,6 +23,7 @@
 
 import com.gitblit.models.AnnotatedLine;
 import com.gitblit.utils.DiffUtils;
+import com.gitblit.utils.DiffUtils.DiffComparator;
 import com.gitblit.utils.DiffUtils.DiffOutputType;
 import com.gitblit.utils.JGitUtils;
 
@@ -40,7 +41,7 @@
 		Repository repository = GitBlitSuite.getHelloworldRepository();
 		RevCommit commit = JGitUtils.getCommit(repository,
 				"1d0c2933a4ae69c362f76797d42d6bd182d05176");
-		String diff = DiffUtils.getCommitDiff(repository, commit, DiffOutputType.PLAIN).content;
+		String diff = DiffUtils.getCommitDiff(repository, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
 		repository.close();
 		assertTrue(diff != null && diff.length() > 0);
 		String expected = "-		system.out.println(\"Hello World\");\n+		System.out.println(\"Hello World\"";
@@ -54,7 +55,7 @@
 				"8baf6a833b5579384d9b9ceb8a16b5d0ea2ec4ca");
 		RevCommit commit = JGitUtils.getCommit(repository,
 				"1d0c2933a4ae69c362f76797d42d6bd182d05176");
-		String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffOutputType.PLAIN).content;
+		String diff = DiffUtils.getDiff(repository, baseCommit, commit, DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
 		repository.close();
 		assertTrue(diff != null && diff.length() > 0);
 		String expected = "-		system.out.println(\"Hello World\");\n+		System.out.println(\"Hello World\"";
@@ -66,7 +67,7 @@
 		Repository repository = GitBlitSuite.getHelloworldRepository();
 		RevCommit commit = JGitUtils.getCommit(repository,
 				"1d0c2933a4ae69c362f76797d42d6bd182d05176");
-		String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffOutputType.PLAIN).content;
+		String diff = DiffUtils.getDiff(repository, commit, "java.java", DiffComparator.SHOW_WHITESPACE, DiffOutputType.PLAIN, 3).content;
 		repository.close();
 		assertTrue(diff != null && diff.length() > 0);
 		String expected = "-		system.out.println(\"Hello World\");\n+		System.out.println(\"Hello World\"";
diff --git a/src/test/java/com/gitblit/tests/FilestoreManagerTest.java b/src/test/java/com/gitblit/tests/FilestoreManagerTest.java
new file mode 100644
index 0000000..c76e9dd
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FilestoreManagerTest.java
@@ -0,0 +1,547 @@
+package com.gitblit.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.Date;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Keys;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.FileUtils;
+
+
+/**
+ * Test of the filestore manager and confirming filesystem updated
+ *
+ * @author Paul Martin
+ *
+ */
+public class FilestoreManagerTest extends GitblitUnitTest {
+
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+	
+	private static final BlobInfo blob_zero = new BlobInfo(0);
+	private static final BlobInfo blob_512KB = new BlobInfo(512*FileUtils.KB);
+	private static final BlobInfo blob_6MB = new BlobInfo(6*FileUtils.MB);
+	
+	private static int download_limit_default = -1;
+	private static int download_limit_test = 5*FileUtils.MB;
+	
+	private static final String invalid_hash_empty = "";
+	private static final String invalid_hash_major = "INVALID_HASH";
+	private static final String invalid_hash_regex_attack = blob_512KB.hash.replace('a', '*');
+	private static final String invalid_hash_one_long = blob_512KB.hash.concat("a");
+	private static final String invalid_hash_one_short = blob_512KB.hash.substring(1);
+	
+
+	
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+		}
+	}
+	
+
+	
+	@Test
+	public void testAdminAccess() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+		ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+		
+		UserModel u = new UserModel("admin");
+		u.canAdmin = true;
+
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+		
+		//Invalid hash tests
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+		
+		// Download prior to upload
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Bad input is rejected with no upload taking place
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.Error_Invalid_Size, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.Error_Hash_Mismatch, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		//Confirm no upload with bad input
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+		
+		//Subsequent failed uploads do not affect file
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+		
+		//Zero length upload is valid
+		assertEquals(Status.Available, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+		assertArrayEquals(blob_zero.blob, streamOut.toByteArray());
+		
+		
+		//Pre-informed upload identifies identical errors as immediate upload
+		assertEquals(Status.Upload_Pending, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.Available, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		assertArrayEquals(blob_6MB.blob, streamOut.toByteArray());
+		
+		//Confirm the relevant files exist
+		assertTrue("Admin did not save zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+		assertTrue("Admin did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertTrue("Admin did not save 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+		//Clear the files and cache to test upload limit property
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+		
+		assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+		
+		assertEquals(Status.Error_Exceeds_Size_Limit, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertTrue("Admin did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertFalse("Admin saved 6MB file despite (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+	}
+	
+	@Test
+	public void testAuthenticatedAccess() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+		r.authorizationControl = AuthorizationControl.AUTHENTICATED;
+		r.accessRestriction = AccessRestrictionType.VIEW;
+		
+		ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+		
+		UserModel u = new UserModel("test");
+
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+		
+		//Invalid hash tests
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+		
+		// Download prior to upload
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Bad input is rejected with no upload taking place
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.Error_Invalid_Size, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.Error_Hash_Mismatch, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		//Confirm no upload with bad input
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+		
+		//Subsequent failed uploads do not affect file
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+		
+		//Zero length upload is valid
+		assertEquals(Status.Available, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+		assertArrayEquals(blob_zero.blob, streamOut.toByteArray());
+		
+		
+		//Pre-informed upload identifies identical errors as immediate upload
+		assertEquals(Status.Upload_Pending, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Invalid_Oid, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Size_Mismatch, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.Available, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		assertArrayEquals(blob_6MB.blob, streamOut.toByteArray());
+		
+		//Confirm the relevant files exist
+		assertTrue("Authenticated user did not save zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+		assertTrue("Authenticated user did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertTrue("Authenticated user did not save 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+		//Clear the files and cache to test upload limit property
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+		
+		assertEquals(Status.Available, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Available, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		assertArrayEquals(blob_512KB.blob, streamOut.toByteArray());
+		
+		assertEquals(Status.Error_Exceeds_Size_Limit, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertTrue("Authenticated user did not save 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertFalse("Authenticated user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+	}
+	
+	@Test
+	public void testAnonymousAccess() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+		r.authorizationControl = AuthorizationControl.NAMED;
+		r.accessRestriction = AccessRestrictionType.CLONE;
+
+		ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+
+		UserModel u = UserModel.ANONYMOUS;
+
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+		
+		//Invalid hash tests
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+		
+		// Download prior to upload
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Bad input is rejected with no upload taking place
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		//Confirm no upload with bad input
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Subsequent failed uploads do not affect file
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Zero length upload is valid
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+		
+		
+		//Pre-informed upload identifies identical errors as immediate upload
+		assertEquals(Status.AuthenticationRequired, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, -1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		//Confirm the relevant files do not exist
+		assertFalse("Anonymous user saved zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+		assertFalse("Anonymous user 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertFalse("Anonymous user 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+		//Clear the files and cache to test upload limit property
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		assertEquals(Status.AuthenticationRequired, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertFalse("Anonymous user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertFalse("Anonymous user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+	}
+	
+	@Test
+	public void testUnauthorizedAccess() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r = new RepositoryModel("myrepo.git", null, null, new Date());
+		r.authorizationControl = AuthorizationControl.NAMED;
+		r.accessRestriction = AccessRestrictionType.VIEW;
+		
+		ByteArrayOutputStream streamOut = new ByteArrayOutputStream();
+		
+		UserModel u = new UserModel("test");
+		u.setRepositoryPermission(r.name, AccessPermission.CLONE);
+		
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_default);
+		
+		//Invalid hash tests
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_major, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_regex_attack, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_long, u, r, streamOut));
+		assertEquals(Status.Error_Invalid_Oid, filestore().downloadBlob(invalid_hash_one_short, u, r, streamOut));
+		
+		// Download prior to upload
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Bad input is rejected with no upload taking place
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_major, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_regex_attack, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_long, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_empty, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_short, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, -1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, 0, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_zero.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+
+		//Confirm no upload with bad input
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Subsequent failed uploads do not affect file
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length-1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length+1, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		//Zero length upload is valid
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_zero.hash, blob_zero.length, u, r, new ByteArrayInputStream(blob_zero.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_zero.hash, u, r, streamOut));
+		
+		
+		//Pre-informed upload identifies identical errors as immediate upload
+		assertEquals(Status.Error_Unauthorized, filestore().addObject(blob_6MB.hash, blob_6MB.length, u, r));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_empty, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_major, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_regex_attack, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_long, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(invalid_hash_one_short, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, -1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, 0, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length-1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length+1, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		//Good input will accept the upload
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		//Confirm the relevant files exist
+		assertFalse("Unauthorized user saved zero length file!", filestore().getStoragePath(blob_zero.hash).exists());
+		assertFalse("Unauthorized user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertFalse("Unauthorized user saved 6MB file!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+		//Clear the files and cache to test upload limit property
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		settings().overrideSetting(Keys.filestore.maxUploadSize, download_limit_test);
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_512KB.hash, blob_512KB.length, u, r, new ByteArrayInputStream(blob_512KB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_512KB.hash, u, r, streamOut));
+		
+		assertEquals(Status.Error_Unauthorized, filestore().uploadBlob(blob_6MB.hash, blob_6MB.length, u, r, new ByteArrayInputStream(blob_6MB.blob)));
+		streamOut.reset();
+		assertEquals(Status.Unavailable, filestore().downloadBlob(blob_6MB.hash, u, r, streamOut));
+		
+		assertFalse("Unauthorized user saved 512KB file!", filestore().getStoragePath(blob_512KB.hash).exists());
+		assertFalse("Unauthorized user saved 6MB file (over filesize limit)!", filestore().getStoragePath(blob_6MB.hash).exists());
+		
+	}
+	
+}
+
+/*
+ * Test helper structure to create blobs of a given size
+ */
+final class BlobInfo {
+	public byte[] blob;
+	public String hash;
+	public int length;
+	
+	public BlobInfo(int nBytes) {
+		blob = new byte[nBytes];
+		new java.util.Random().nextBytes(blob);
+		hash = DigestUtils.sha256Hex(blob);
+		length = nBytes;
+	}
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/FilestoreServletTest.java b/src/test/java/com/gitblit/tests/FilestoreServletTest.java
new file mode 100644
index 0000000..4e4b056
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/FilestoreServletTest.java
@@ -0,0 +1,355 @@
+package com.gitblit.tests;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.entity.ByteArrayEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.gitblit.Keys;
+import com.gitblit.manager.FilestoreManager;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.models.FilestoreModel.Status;
+import com.gitblit.servlet.FilestoreServlet;
+import com.gitblit.utils.FileUtils;
+
+public class FilestoreServletTest extends GitblitUnitTest {
+	
+	private static final AtomicBoolean started = new AtomicBoolean(false);
+	
+	private static final String SHA256_EG = "9a712c5d4037503a2d5ee1d07ad191eb99d051e84cbb020c171a5ae19bbe3cbd";
+	
+	private static final String repoName = "helloworld.git";
+	
+    private static final String repoLfs = "/r/" + repoName + "/info/lfs/objects/";
+	
+	@BeforeClass
+	public static void startGitblit() throws Exception {
+		started.set(GitBlitSuite.startGitblit());
+	}
+
+	@AfterClass
+	public static void stopGitblit() throws Exception {
+		if (started.get()) {
+			GitBlitSuite.stopGitblit();
+		}
+	}
+	
+	
+	@Test
+	public void testRegexGroups() throws Exception {
+		
+		Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH);
+		
+		String basicUrl = "https://localhost:8080/r/test.git/info/lfs/objects/";
+		String batchUrl = basicUrl + "batch";
+		String oidUrl = basicUrl + SHA256_EG; 
+		
+        Matcher m = p.matcher(batchUrl);
+        assertTrue(m.find());
+        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+        assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+        assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+        
+        m = p.matcher(oidUrl);
+        assertTrue(m.find());
+        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+        assertEquals("test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+        assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+	}
+	
+	@Test
+	public void testRegexGroupsNestedRepo() throws Exception {
+		
+		Pattern p = Pattern.compile(FilestoreServlet.REGEX_PATH);
+		
+		String basicUrl = "https://localhost:8080/r/nested/test.git/info/lfs/objects/";
+		String batchUrl = basicUrl + "batch";
+		String oidUrl = basicUrl + SHA256_EG; 
+		
+        Matcher m = p.matcher(batchUrl);
+        assertTrue(m.find());
+        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+        assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+        assertEquals("batch", m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+        
+        m = p.matcher(oidUrl);
+        assertTrue(m.find());
+        assertEquals("https://localhost:8080", m.group(FilestoreServlet.REGEX_GROUP_BASE_URI));
+        assertEquals("r", m.group(FilestoreServlet.REGEX_GROUP_PREFIX));
+        assertEquals("nested/test.git", m.group(FilestoreServlet.REGEX_GROUP_REPOSITORY));
+        assertEquals(SHA256_EG, m.group(FilestoreServlet.REGEX_GROUP_ENDPOINT));
+	}
+	
+	@Test
+	public void testDownload() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r =  gitblit().getRepositoryModel(repoName);
+		
+		UserModel u = new UserModel("admin");
+		u.canAdmin = true;
+
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+		
+		//Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods
+		assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob)));
+		
+        final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
+        
+        HttpClient client = HttpClientBuilder.create().build();
+    	HttpGet request = new HttpGet(downloadURL);
+
+    	// add request header
+    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+    	HttpResponse response = client.execute(request);
+    	
+		assertEquals(200, response.getStatusLine().getStatusCode());
+
+		String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+		
+		String expectedContent = String.format("{%s:%s,%s:%d,%s:{%s:{%s:%s}}}",
+				"\"oid\"", "\"" + blob.hash + "\"",
+				"\"size\"", blob.length,
+				"\"actions\"",
+				"\"download\"",
+				"\"href\"", "\"" + downloadURL + "\"");
+		
+		assertEquals(expectedContent, content);
+		
+		
+		//Now try the binary download
+		request.removeHeaders(HttpHeaders.ACCEPT);
+		response = client.execute(request);
+		
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		
+		byte[] dlData = IOUtils.toByteArray(response.getEntity().getContent());
+				
+		assertArrayEquals(blob.blob,  dlData);
+		
+	}
+	
+	@Test
+	public void testDownloadMultiple() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r =  gitblit().getRepositoryModel(repoName);
+		
+		UserModel u = new UserModel("admin");
+		u.canAdmin = true;
+
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+		
+		//Emulate a pre-existing Git-LFS repository by using using internal pre-tested methods
+		assertEquals(Status.Available, filestore().uploadBlob(blob.hash, blob.length, u, r, new ByteArrayInputStream(blob.blob)));
+		
+        final String batchURL = GitBlitSuite.url + repoLfs + "batch";
+		final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
+        
+        HttpClient client = HttpClientBuilder.create().build();
+    	HttpPost request = new HttpPost(batchURL);
+
+    	// add request header
+    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+    	request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
+    	
+    	String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d},{%s:%s,%s:%d}]}",
+    			"\"operation\"", "\"download\"",
+    			"\"objects\"",
+    			"\"oid\"", "\"" + blob.hash + "\"",
+    			"\"size\"", blob.length,
+    			"\"oid\"", "\"" + SHA256_EG + "\"",
+    			"\"size\"", 0);
+    	
+    	HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
+    	request.setEntity(entity);
+
+    	HttpResponse response = client.execute(request);
+    	
+    	String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+		assertEquals(200, response.getStatusLine().getStatusCode());
+
+		String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}},{%s:%s,%s:%d,%s:{%s:%s,%s:%d}}]}",
+				"\"objects\"",
+				"\"oid\"", "\"" + blob.hash + "\"",
+				"\"size\"", blob.length,
+				"\"actions\"",
+				"\"download\"",
+				"\"href\"", "\"" + downloadURL + "\"",
+				"\"oid\"", "\"" + SHA256_EG + "\"",
+				"\"size\"", 0,
+				"\"error\"",
+				"\"message\"", "\"Object not available\"",
+				"\"code\"", 404
+				);
+		
+		assertEquals(expectedContent, responseMessage);
+	}
+	
+	@Test
+	public void testDownloadUnavailable() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+		
+        final String downloadURL = GitBlitSuite.url + repoLfs + blob.hash;
+        
+        HttpClient client = HttpClientBuilder.create().build();
+    	HttpGet request = new HttpGet(downloadURL);
+
+    	// add request header
+    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+    	HttpResponse response = client.execute(request);
+    	
+		assertEquals(404, response.getStatusLine().getStatusCode());
+
+		String content = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+		
+		String expectedError = String.format("{%s:%s,%s:%d}",
+				"\"message\"", "\"Object not available\"",
+				"\"code\"", 404);
+		
+		assertEquals(expectedError, content);
+	}
+	
+	@Test
+	public void testUpload() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		RepositoryModel r =  gitblit().getRepositoryModel(repoName);
+		
+		UserModel u = new UserModel("admin");
+		u.canAdmin = true;
+
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+        
+        final String expectedUploadURL = GitBlitSuite.url + repoLfs + blob.hash;
+        final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch";
+        
+        HttpClient client = HttpClientBuilder.create().build();
+    	HttpPost request = new HttpPost(initialUploadURL);
+
+    	// add request header
+    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+    	request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
+    	
+    	String content = String.format("{%s:%s,%s:[{%s:%s,%s:%d}]}",
+    			"\"operation\"", "\"upload\"",
+    			"\"objects\"",
+    			"\"oid\"", "\"" + blob.hash + "\"",
+    			"\"size\"", blob.length);
+    	
+    	HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
+    	request.setEntity(entity);
+    	
+    	HttpResponse response = client.execute(request);
+    	String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+		assertEquals(200, response.getStatusLine().getStatusCode());
+
+		String expectedContent = String.format("{%s:[{%s:%s,%s:%d,%s:{%s:{%s:%s}}}]}",
+				"\"objects\"",
+				"\"oid\"", "\"" + blob.hash + "\"",
+				"\"size\"", blob.length,
+				"\"actions\"",
+				"\"upload\"",
+				"\"href\"", "\"" + expectedUploadURL + "\"");
+		
+		assertEquals(expectedContent, responseMessage);
+		
+		
+		//Now try to upload the binary download
+		HttpPut putRequest = new HttpPut(expectedUploadURL);
+		putRequest.setEntity(new ByteArrayEntity(blob.blob));
+		response = client.execute(putRequest);
+		
+		responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+		
+		assertEquals(200, response.getStatusLine().getStatusCode());
+		
+		//Confirm behind the scenes that it is available
+		ByteArrayOutputStream savedBlob = new ByteArrayOutputStream();
+		assertEquals(Status.Available, filestore().downloadBlob(blob.hash, u, r, savedBlob));
+		assertArrayEquals(blob.blob,  savedBlob.toByteArray());
+	}
+
+	@Test
+	public void testMalformedUpload() throws Exception {
+		
+		FileUtils.delete(filestore().getStorageFolder());
+		filestore().clearFilestoreCache();
+		
+		//No upload limit
+		settings().overrideSetting(Keys.filestore.maxUploadSize, FilestoreManager.UNDEFINED_SIZE);
+
+		final BlobInfo blob = new BlobInfo(512*FileUtils.KB);
+        
+        final String initialUploadURL = GitBlitSuite.url + repoLfs + "batch";
+        
+        HttpClient client = HttpClientBuilder.create().build();
+    	HttpPost request = new HttpPost(initialUploadURL);
+
+    	// add request header
+    	request.addHeader(HttpHeaders.ACCEPT, FilestoreServlet.GIT_LFS_META_MIME);
+    	request.addHeader(HttpHeaders.CONTENT_ENCODING, FilestoreServlet.GIT_LFS_META_MIME);
+    	
+    	//Malformed JSON, comma instead of colon and unquoted strings
+    	String content = String.format("{%s:%s,%s:[{%s:%s,%s,%d}]}",
+    			"operation", "upload",
+    			"objects",
+    			"oid", blob.hash,
+    			"size", blob.length);
+    	
+    	HttpEntity entity = new ByteArrayEntity(content.getBytes("UTF-8"));
+    	request.setEntity(entity);
+    	
+    	HttpResponse response = client.execute(request);
+    	String responseMessage = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
+		assertEquals(400, response.getStatusLine().getStatusCode());
+		
+		String expectedError = String.format("{%s:%s,%s:%d}",
+				"\"message\"", "\"Malformed Git-LFS request\"",
+				"\"code\"", 400);
+				
+		assertEquals(expectedError, responseMessage);
+	}
+	
+}
diff --git a/src/test/java/com/gitblit/tests/GitBlitSuite.java b/src/test/java/com/gitblit/tests/GitBlitSuite.java
index 5a7dcea..b01c82c 100644
--- a/src/test/java/com/gitblit/tests/GitBlitSuite.java
+++ b/src/test/java/com/gitblit/tests/GitBlitSuite.java
@@ -63,9 +63,10 @@
 		GitBlitTest.class, FederationTests.class, RpcTests.class, GitServletTest.class, GitDaemonTest.class,
 		SshDaemonTest.class, GroovyScriptTest.class, LuceneExecutorTest.class, RepositoryModelTest.class,
 		FanoutServiceTest.class, Issue0259Test.class, Issue0271Test.class, HtpasswdAuthenticationTest.class,
-		ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
+		ModelUtilsTest.class, JnaUtilsTest.class, LdapSyncServiceTest.class, FileTicketServiceTest.class,
 		BranchTicketServiceTest.class, RedisTicketServiceTest.class, AuthenticationManagerTest.class,
-		SshKeysDispatcherTest.class })
+		SshKeysDispatcherTest.class, UITicketTest.class, PathUtilsTest.class, SshKerberosAuthenticationTest.class,
+		GravatarTest.class, FilestoreManagerTest.class, FilestoreServletTest.class })
 public class GitBlitSuite {
 
 	public static final File BASEFOLDER = new File("data");
@@ -110,11 +111,11 @@
 		return getRepository("test/gitective.git");
 	}
 
-	public static Repository getTicketsTestRepository() {
-		JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close();
-		return getRepository("gb-tickets.git");
-	}
-
+	public static Repository getTicketsTestRepository() {
+		JGitUtils.createRepository(REPOSITORIES, "gb-tickets.git").close();
+		return getRepository("gb-tickets.git");
+	}
+
 	private static Repository getRepository(String name) {
 		try {
 			File gitDir = FileKey.resolve(new File(REPOSITORIES, name), FS.DETECTED);
diff --git a/src/test/java/com/gitblit/tests/GitBlitTest.java b/src/test/java/com/gitblit/tests/GitBlitTest.java
index 9eaae7f..e873ced 100644
--- a/src/test/java/com/gitblit/tests/GitBlitTest.java
+++ b/src/test/java/com/gitblit/tests/GitBlitTest.java
@@ -176,7 +176,7 @@
 
 	@Test
 	public void testAuthentication() throws Exception {
-		assertTrue(authentication().authenticate("admin", "admin".toCharArray()) != null);
+		assertTrue(authentication().authenticate("admin", "admin".toCharArray(), null) != null);
 	}
 
 	@Test
diff --git a/src/test/java/com/gitblit/tests/GitblitUnitTest.java b/src/test/java/com/gitblit/tests/GitblitUnitTest.java
index 9dceaaf..58bc60e 100644
--- a/src/test/java/com/gitblit/tests/GitblitUnitTest.java
+++ b/src/test/java/com/gitblit/tests/GitblitUnitTest.java
@@ -18,6 +18,7 @@
 import com.gitblit.IStoredSettings;
 import com.gitblit.manager.IAuthenticationManager;
 import com.gitblit.manager.IFederationManager;
+import com.gitblit.manager.IFilestoreManager;
 import com.gitblit.manager.IGitblit;
 import com.gitblit.manager.INotificationManager;
 import com.gitblit.manager.IProjectManager;
@@ -64,4 +65,8 @@
 	public static IGitblit gitblit() {
 		return GitblitContext.getManager(IGitblit.class);
 	}
+	
+	public static IFilestoreManager filestore() {
+		return GitblitContext.getManager(IFilestoreManager.class); 
+	}
 }
diff --git a/src/test/java/com/gitblit/tests/GravatarTest.java b/src/test/java/com/gitblit/tests/GravatarTest.java
new file mode 100644
index 0000000..ba989d5
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/GravatarTest.java
@@ -0,0 +1,74 @@
+package com.gitblit.tests;
+
+import org.junit.Test;
+
+import com.gitblit.AvatarGenerator;
+import com.gitblit.GravatarGenerator;
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.guice.AvatarGeneratorProvider;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.utils.ActivityUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+public class GravatarTest extends GitblitUnitTest {
+
+	public static  class AvatarModule extends AbstractModule {
+		private final IStoredSettings settings;
+
+		AvatarModule(IStoredSettings settings) {
+			this.settings = settings;
+		}
+
+		@Override
+		protected void configure() {
+			bind(IStoredSettings.class).toInstance(settings);
+			bind(XssFilter.class).to(AllowXssFilter.class);
+			bind(IRuntimeManager.class).to(RuntimeManager.class);
+			bind(AvatarGenerator.class).toProvider(AvatarGeneratorProvider.class);
+		}
+	}
+
+	@Test
+	public void gravatarIdenticonTest() {
+		IStoredSettings settings = new MemorySettings();
+		settings.overrideSetting(Keys.web.avatarClass, GravatarGenerator.class.getName());
+
+		Injector injector = Guice.createInjector(new AvatarModule(settings));
+		AvatarGenerator avatarGenerator = injector.getInstance(AvatarGenerator.class);
+
+		String username = "username";
+		String emailAddress = "emailAddress";
+		int width = 10;
+
+		String url = avatarGenerator.getURL(username, emailAddress, true, width);
+		assertNotNull(url);
+
+		assertEquals(ActivityUtils.getGravatarIdenticonUrl(emailAddress, width), url);
+	}
+
+	@Test
+	public void gravatarThumbnailTest() {
+		IStoredSettings settings = new MemorySettings();
+		settings.overrideSetting(Keys.web.avatarClass, GravatarGenerator.class.getName());
+
+		Injector injector = Guice.createInjector(new AvatarModule(settings));
+		AvatarGenerator avatarGenerator = injector.getInstance(AvatarGenerator.class);
+
+		String username = "username";
+		String emailAddress = "emailAddress";
+		int width = 10;
+
+		String url = avatarGenerator.getURL(username, emailAddress, false, width);
+		assertNotNull(url);
+
+		assertEquals(ActivityUtils.getGravatarThumbnailUrl(emailAddress, width), url);
+	}
+
+}
diff --git a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
index e2bb764..26a49b2 100644
--- a/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
+++ b/src/test/java/com/gitblit/tests/HtpasswdAuthenticationTest.java
@@ -200,43 +200,43 @@
     public void testAuthenticationManager()
     {
         MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
-        UserModel user = auth.authenticate("user1", "pass1".toCharArray());
+        UserModel user = auth.authenticate("user1", "pass1".toCharArray(), null);
         assertNotNull(user);
         assertEquals("user1", user.username);
 
-        user = auth.authenticate("user2", "pass2".toCharArray());
+        user = auth.authenticate("user2", "pass2".toCharArray(), null);
         assertNotNull(user);
         assertEquals("user2", user.username);
 
         // Test different encryptions
-        user = auth.authenticate("plain", "passWord".toCharArray());
+        user = auth.authenticate("plain", "passWord".toCharArray(), null);
         assertNotNull(user);
         assertEquals("plain", user.username);
 
         MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
-        user = auth.authenticate("crypt", "password".toCharArray());
+        user = auth.authenticate("crypt", "password".toCharArray(), null);
         assertNotNull(user);
         assertEquals("crypt", user.username);
 
-        user = auth.authenticate("md5", "password".toCharArray());
+        user = auth.authenticate("md5", "password".toCharArray(), null);
         assertNotNull(user);
         assertEquals("md5", user.username);
 
-        user = auth.authenticate("sha", "password".toCharArray());
+        user = auth.authenticate("sha", "password".toCharArray(), null);
         assertNotNull(user);
         assertEquals("sha", user.username);
 
 
         // Test leading and trailing whitespace
-        user = auth.authenticate("trailing", "whitespace".toCharArray());
+        user = auth.authenticate("trailing", "whitespace".toCharArray(), null);
         assertNotNull(user);
         assertEquals("trailing", user.username);
 
-        user = auth.authenticate("tabbed", "frontAndBack".toCharArray());
+        user = auth.authenticate("tabbed", "frontAndBack".toCharArray(), null);
         assertNotNull(user);
         assertEquals("tabbed", user.username);
 
-        user = auth.authenticate("leading", "whitespace".toCharArray());
+        user = auth.authenticate("leading", "whitespace".toCharArray(), null);
         assertNotNull(user);
         assertEquals("leading", user.username);
     }
@@ -323,55 +323,55 @@
     {
         UserModel user = null;
         MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "true");
-        user = auth.authenticate("user1", "".toCharArray());
+        user = auth.authenticate("user1", "".toCharArray(), null);
         assertNull("User 'user1' falsely authenticated.", user);
 
-        user = auth.authenticate("user1", "pass2".toCharArray());
+        user = auth.authenticate("user1", "pass2".toCharArray(), null);
         assertNull("User 'user1' falsely authenticated.", user);
 
-        user = auth.authenticate("user2", "lalala".toCharArray());
+        user = auth.authenticate("user2", "lalala".toCharArray(), null);
         assertNull("User 'user2' falsely authenticated.", user);
 
 
-        user = auth.authenticate("user3", "disabled".toCharArray());
+        user = auth.authenticate("user3", "disabled".toCharArray(), null);
         assertNull("User 'user3' falsely authenticated.", user);
 
-        user = auth.authenticate("user4", "disabled".toCharArray());
+        user = auth.authenticate("user4", "disabled".toCharArray(), null);
         assertNull("User 'user4' falsely authenticated.", user);
 
 
-        user = auth.authenticate("plain", "text".toCharArray());
+        user = auth.authenticate("plain", "text".toCharArray(), null);
         assertNull("User 'plain' falsely authenticated.", user);
 
-        user = auth.authenticate("plain", "password".toCharArray());
+        user = auth.authenticate("plain", "password".toCharArray(), null);
         assertNull("User 'plain' falsely authenticated.", user);
 
 
         MS.put(KEY_SUPPORT_PLAINTEXT_PWD, "false");
 
-        user = auth.authenticate("crypt", "".toCharArray());
+        user = auth.authenticate("crypt", "".toCharArray(), null);
         assertNull("User 'cyrpt' falsely authenticated.", user);
 
-        user = auth.authenticate("crypt", "passwd".toCharArray());
+        user = auth.authenticate("crypt", "passwd".toCharArray(), null);
         assertNull("User 'crypt' falsely authenticated.", user);
 
-        user = auth.authenticate("md5", "".toCharArray());
+        user = auth.authenticate("md5", "".toCharArray(), null);
         assertNull("User 'md5' falsely authenticated.", user);
 
-        user = auth.authenticate("md5", "pwd".toCharArray());
+        user = auth.authenticate("md5", "pwd".toCharArray(), null);
         assertNull("User 'md5' falsely authenticated.", user);
 
-        user = auth.authenticate("sha", "".toCharArray());
+        user = auth.authenticate("sha", "".toCharArray(), null);
         assertNull("User 'sha' falsely authenticated.", user);
 
-        user = auth.authenticate("sha", "letmein".toCharArray());
+        user = auth.authenticate("sha", "letmein".toCharArray(), null);
         assertNull("User 'sha' falsely authenticated.", user);
 
 
-        user = auth.authenticate("  tabbed", "frontAndBack".toCharArray());
+        user = auth.authenticate("  tabbed", "frontAndBack".toCharArray(), null);
         assertNull("User 'tabbed' falsely authenticated.", user);
 
-        user = auth.authenticate("    leading", "whitespace".toCharArray());
+        user = auth.authenticate("    leading", "whitespace".toCharArray(), null);
         assertNull("User 'leading' falsely authenticated.", user);
     }
 
diff --git a/src/test/java/com/gitblit/tests/JGitUtilsTest.java b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
index c2cfb33..2cf4a5a 100644
--- a/src/test/java/com/gitblit/tests/JGitUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/JGitUtilsTest.java
@@ -476,6 +476,15 @@
 	}
 
 	@Test
+	public void testFilesInPath2() throws Exception {
+		assertEquals(0, JGitUtils.getFilesInPath2(null, null, null).size());
+		Repository repository = GitBlitSuite.getHelloworldRepository();
+		List<PathModel> files = JGitUtils.getFilesInPath2(repository, null, null);
+		repository.close();
+		assertTrue(files.size() > 10);
+	}
+
+	@Test
 	public void testDocuments() throws Exception {
 		Repository repository = GitBlitSuite.getTicgitRepository();
 		List<String> extensions = Arrays.asList(new String[] { ".mkd", ".md" });
diff --git a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
index 7c84ecc..84dd138 100644
--- a/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
+++ b/src/test/java/com/gitblit/tests/LdapAuthenticationTest.java
@@ -240,23 +240,23 @@
 
 	@Test
 	public void testAuthenticationManager() {
-		UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray());
+		UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null);
 		assertNotNull(userOneModel);
 		assertNotNull(userOneModel.getTeam("git_admins"));
 		assertNotNull(userOneModel.getTeam("git_users"));
 		assertTrue(userOneModel.canAdmin);
 
-		UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray());
+		UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null);
 		assertNull(userOneModelFailedAuth);
 
-		UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray());
+		UserModel userTwoModel = auth.authenticate("UserTwo", "userTwoPassword".toCharArray(), null);
 		assertNotNull(userTwoModel);
 		assertNotNull(userTwoModel.getTeam("git_users"));
 		assertNull(userTwoModel.getTeam("git_admins"));
 		assertNotNull(userTwoModel.getTeam("git admins"));
 		assertTrue(userTwoModel.canAdmin);
 
-		UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray());
+		UserModel userThreeModel = auth.authenticate("UserThree", "userThreePassword".toCharArray(), null);
 		assertNotNull(userThreeModel);
 		assertNotNull(userThreeModel.getTeam("git_users"));
 		assertNull(userThreeModel.getTeam("git_admins"));
@@ -269,10 +269,10 @@
 		settings.put(Keys.realm.ldap.username, "");
 		settings.put(Keys.realm.ldap.password, "");
 
-		UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray());
+		UserModel userOneModel = auth.authenticate("UserOne", "userOnePassword".toCharArray(), null);
 		assertNotNull(userOneModel);
 
-		UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray());
+		UserModel userOneModelFailedAuth = auth.authenticate("UserOne", "userTwoPassword".toCharArray(), null);
 		assertNull(userOneModelFailedAuth);
 	}
 
diff --git a/src/test/java/com/gitblit/tests/PathUtilsTest.java b/src/test/java/com/gitblit/tests/PathUtilsTest.java
new file mode 100644
index 0000000..209b8ee
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/PathUtilsTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2011 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.tests;
+
+import com.gitblit.utils.PathUtils;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class PathUtilsTest extends GitblitUnitTest {
+
+	private static final String[][][] testData = {
+
+			{
+					// Folder contents
+					{".gitignore","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"},
+					// Expected after compressing
+					{".gitignore", "src/main/java/", "docs/"}
+			},
+
+			{
+					{".gitignore","src/main/java/a.java", "src/main/b.java", "docs/c.md"},
+					{".gitignore", "src/main/", "docs/"}
+			},
+
+			{
+					{".gitignore","src/x.java","src/main/java/a.java", "src/main/java/b.java", "docs/c.md"},
+					{".gitignore", "src/", "docs/"}
+			},
+	};
+
+
+
+
+	@Test
+	public void testCompressPaths() throws Exception {
+
+		for (String[][] test : testData ) {
+			assertArrayEquals(test[1], PathUtils.compressPaths(Arrays.asList(test[0])).toArray(new String[]{}));
+		}
+
+	}
+
+	@Test
+	public void testGetLastPathComponent() {
+		assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/e.out"), "e.out");
+		assertEquals(PathUtils.getLastPathComponent("e.out"), "e.out");
+		assertEquals(PathUtils.getLastPathComponent("/a/b/c/d/"), "d");
+		assertEquals(PathUtils.getLastPathComponent("/a/b/c/d"), "d");
+		assertEquals(PathUtils.getLastPathComponent("/"), "/");
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
index ad773b7..7136fa7 100644
--- a/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
+++ b/src/test/java/com/gitblit/tests/RedmineAuthenticationTest.java
@@ -65,7 +65,7 @@
     @Test
     public void testAuthenticationManager() throws Exception {
     	AuthenticationManager auth = newAuthenticationManager();
-        UserModel userModel = auth.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray());
+        UserModel userModel = auth.authenticate("RedmineAdminId", "RedmineAPIKey".toCharArray(), null);
         assertThat(userModel.getName(), is("redmineadminid"));
         assertThat(userModel.getDisplayName(), is("baz foo"));
         assertThat(userModel.emailAddress, is("baz@example.com"));
diff --git a/src/test/java/com/gitblit/tests/SshDaemonTest.java b/src/test/java/com/gitblit/tests/SshDaemonTest.java
index dcaeaff..c5deb7d 100644
--- a/src/test/java/com/gitblit/tests/SshDaemonTest.java
+++ b/src/test/java/com/gitblit/tests/SshDaemonTest.java
@@ -19,8 +19,8 @@
 import java.text.MessageFormat;
 import java.util.List;
 
-import org.apache.sshd.ClientSession;
-import org.apache.sshd.SshClient;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.session.ClientSession;
 import org.eclipse.jgit.api.CloneCommand;
 import org.eclipse.jgit.api.Git;
 import org.eclipse.jgit.revwalk.RevCommit;
diff --git a/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java b/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java
new file mode 100644
index 0000000..4ba16b6
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/SshKerberosAuthenticationTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 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.tests;
+
+import org.apache.sshd.server.auth.gss.GSSAuthenticator;
+import org.apache.sshd.server.session.ServerSession;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import com.gitblit.manager.AuthenticationManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.models.UserModel;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.transport.ssh.SshDaemonClient;
+import com.gitblit.transport.ssh.SshKrbAuthenticator;
+
+public class SshKerberosAuthenticationTest extends GitblitUnitTest {
+
+	private static class UserModelWrapper {
+		public UserModel um;
+	}
+
+	@Test
+	public void testUserManager() {
+		IRuntimeManager rm = Mockito.mock(IRuntimeManager.class);
+
+		//Build an UserManager that can build a UserModel
+		IUserManager im = Mockito.mock(IUserManager.class);
+		Mockito.doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) {
+				Object[] args = invocation.getArguments();
+				String user = (String) args[0];
+				return new UserModel(user);
+			}
+		}).when(im).getUserModel(Mockito.anyString());
+
+		AuthenticationManager am = new AuthenticationManager(rm, im);
+
+		GSSAuthenticator gssAuthenticator = new SshKrbAuthenticator(new MemorySettings(), am);
+
+		ServerSession session = Mockito.mock(ServerSession.class);
+
+		//Build an SshDaemonClient that can set and get the UserModel
+		final UserModelWrapper umw = new UserModelWrapper();
+		SshDaemonClient client = Mockito.mock(SshDaemonClient.class);
+		Mockito.when(client.getUser()).thenReturn(umw.um);
+		Mockito.doAnswer(new Answer<Object>() {
+			@Override
+			public Object answer(InvocationOnMock invocation) {
+				Object[] args = invocation.getArguments();
+				UserModel um = (UserModel) args[0];
+				umw.um = um;
+				return null;
+			}
+		}).when(client).setUser(Mockito.any(UserModel.class));
+
+		Mockito.when(session.getAttribute(SshDaemonClient.KEY)).thenReturn(client);
+		Assert.assertTrue(gssAuthenticator.validateIdentity(session, "jhappy"));
+
+	}
+}
diff --git a/src/test/java/com/gitblit/tests/SshUnitTest.java b/src/test/java/com/gitblit/tests/SshUnitTest.java
index 43b51b7..27b4ec7 100644
--- a/src/test/java/com/gitblit/tests/SshUnitTest.java
+++ b/src/test/java/com/gitblit/tests/SshUnitTest.java
@@ -26,10 +26,10 @@
 import java.security.PublicKey;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import org.apache.sshd.ClientChannel;
-import org.apache.sshd.ClientSession;
-import org.apache.sshd.SshClient;
 import org.apache.sshd.client.ServerKeyVerifier;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.session.ClientSession;
 import org.apache.sshd.common.util.SecurityUtils;
 import org.junit.After;
 import org.junit.AfterClass;
diff --git a/src/test/java/com/gitblit/tests/StringUtilsTest.java b/src/test/java/com/gitblit/tests/StringUtilsTest.java
index 0fd42aa..7176b88 100644
--- a/src/test/java/com/gitblit/tests/StringUtilsTest.java
+++ b/src/test/java/com/gitblit/tests/StringUtilsTest.java
@@ -50,7 +50,7 @@
 	public void testEscapeForHtml() throws Exception {
 		String input = "& < > \" \t";
 		String outputNoChange = "&amp; &lt; &gt; &quot; \t";
-		String outputChange = "&amp;&nbsp;&lt;&nbsp;&gt;&nbsp;&quot;&nbsp; &nbsp; &nbsp;";
+		String outputChange = "&amp;&nbsp;&lt;&nbsp;&gt;&nbsp;&quot;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
 		assertEquals(outputNoChange, StringUtils.escapeForHtml(input, false));
 		assertEquals(outputChange, StringUtils.escapeForHtml(input, true));
 	}
diff --git a/src/test/java/com/gitblit/tests/TicketServiceTest.java b/src/test/java/com/gitblit/tests/TicketServiceTest.java
index 1676e34..c654383 100644
--- a/src/test/java/com/gitblit/tests/TicketServiceTest.java
+++ b/src/test/java/com/gitblit/tests/TicketServiceTest.java
@@ -293,9 +293,47 @@
 			assertTrue("failed to delete label " + label.name, service.deleteLabel(getRepository(), label.name, "lucifer"));
 		}
 	}
-
-
-
+	
+	@Test
+	public void testPriorityAndSeverity() throws Exception {
+		// C1: create and insert a ticket
+		Change c1 = newChange("testPriorityAndSeverity() " + Long.toHexString(System.currentTimeMillis()));
+		TicketModel ticket = service.createTicket(getRepository(), c1);
+		assertTrue(ticket.number > 0);
+		assertEquals(TicketModel.Priority.Normal, ticket.priority);
+		assertEquals(TicketModel.Severity.Unrated, ticket.severity);
+		
+		TicketModel constructed = service.getTicket(getRepository(), ticket.number);
+		compare(ticket, constructed);
+		
+		// C2: Change Priority max
+		Change c2 = new Change("C2");
+		c2.setField(Field.priority, TicketModel.Priority.Urgent);
+		constructed = service.updateTicket(getRepository(), ticket.number, c2);
+		assertNotNull(constructed);
+		assertEquals(2, constructed.changes.size());
+		assertEquals(TicketModel.Priority.Urgent, constructed.priority);
+		assertEquals(TicketModel.Severity.Unrated, constructed.severity);
+		
+		// C3: Change Severity max
+		Change c3 = new Change("C3");
+		c3.setField(Field.severity, TicketModel.Severity.Catastrophic);
+		constructed = service.updateTicket(getRepository(), ticket.number, c3);
+		assertNotNull(constructed);
+		assertEquals(3, constructed.changes.size());
+		assertEquals(TicketModel.Priority.Urgent, constructed.priority);
+		assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
+		
+		// C4: Change Priority min
+		Change c4 = new Change("C3");
+		c4.setField(Field.priority, TicketModel.Priority.Low);
+		constructed = service.updateTicket(getRepository(), ticket.number, c4);
+		assertNotNull(constructed);
+		assertEquals(4, constructed.changes.size());
+		assertEquals(TicketModel.Priority.Low, constructed.priority);
+		assertEquals(TicketModel.Severity.Catastrophic, constructed.severity);
+	}
+	
 	private Change newChange(String summary) {
 		Change change = new Change("C1");
 		change.setField(Field.title, summary);
diff --git a/src/test/java/com/gitblit/tests/UITicketTest.java b/src/test/java/com/gitblit/tests/UITicketTest.java
new file mode 100644
index 0000000..54aa1e1
--- /dev/null
+++ b/src/test/java/com/gitblit/tests/UITicketTest.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2014 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.tests;
+
+import java.io.File;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.bouncycastle.util.Arrays;
+import org.eclipse.jgit.lib.Repository;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.manager.INotificationManager;
+import com.gitblit.manager.IPluginManager;
+import com.gitblit.manager.IRepositoryManager;
+import com.gitblit.manager.IRuntimeManager;
+import com.gitblit.manager.IUserManager;
+import com.gitblit.manager.NotificationManager;
+import com.gitblit.manager.PluginManager;
+import com.gitblit.manager.RepositoryManager;
+import com.gitblit.manager.RuntimeManager;
+import com.gitblit.manager.UserManager;
+import com.gitblit.models.Mailing;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Attachment;
+import com.gitblit.models.TicketModel.Change;
+import com.gitblit.models.TicketModel.Field;
+import com.gitblit.models.TicketModel.Patchset;
+import com.gitblit.models.TicketModel.Priority;
+import com.gitblit.models.TicketModel.Severity;
+import com.gitblit.models.TicketModel.Status;
+import com.gitblit.models.TicketModel.Type;
+import com.gitblit.tests.mock.MemorySettings;
+import com.gitblit.tickets.ITicketService;
+import com.gitblit.tickets.ITicketService.TicketFilter;
+import com.gitblit.tickets.QueryResult;
+import com.gitblit.tickets.TicketIndexer.Lucene;
+import com.gitblit.tickets.BranchTicketService;
+import com.gitblit.tickets.TicketLabel;
+import com.gitblit.tickets.TicketMilestone;
+import com.gitblit.tickets.TicketNotifier;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.XssFilter;
+import com.gitblit.utils.XssFilter.AllowXssFilter;
+
+/**
+ * Generates the range of tickets to ease testing of the look and feel of tickets
+ */
+public class UITicketTest extends GitblitUnitTest {
+
+	private ITicketService service;	
+	final String repoName = "UITicketTest.git";
+	final RepositoryModel repo = new RepositoryModel(repoName, null, null, null);
+	
+	protected ITicketService getService(boolean deleteAll) throws Exception {
+
+		IStoredSettings settings = getSettings(deleteAll);
+		XssFilter xssFilter = new AllowXssFilter();
+		IRuntimeManager runtimeManager = new RuntimeManager(settings, xssFilter).start();
+		IPluginManager pluginManager = new PluginManager(runtimeManager).start();
+		INotificationManager notificationManager = new NotificationManager(settings).start();
+		IUserManager userManager = new UserManager(runtimeManager, pluginManager).start();
+		IRepositoryManager repositoryManager = new RepositoryManager(runtimeManager, pluginManager, userManager).start();
+
+		BranchTicketService service = new BranchTicketService(
+				runtimeManager,
+				pluginManager,
+				notificationManager,
+				userManager,
+				repositoryManager).start();
+
+		if (deleteAll) {
+			service.deleteAll(repo);
+		}
+		return service;
+	}
+	
+	protected IStoredSettings getSettings(boolean deleteAll) throws Exception {
+		File dir = new File(GitBlitSuite.REPOSITORIES, repoName);
+		if (deleteAll) {
+			FileUtils.deleteDirectory(dir);
+			JGitUtils.createRepository(GitBlitSuite.REPOSITORIES, repoName).close();
+		}
+		
+		File luceneDir = new File(dir, "tickets/lucene");
+		luceneDir.mkdirs();
+
+		Map<String, Object> map = new HashMap<String, Object>();
+		map.put(Keys.git.repositoriesFolder, GitBlitSuite.REPOSITORIES.getAbsolutePath());
+		map.put(Keys.tickets.indexFolder, luceneDir.getAbsolutePath());
+
+		IStoredSettings settings = new MemorySettings(map);
+		return settings;
+	}
+
+	@Before
+	public void setup() throws Exception {
+		service = getService(true);
+	}
+
+	@After
+	public void cleanup() {
+		service.stop();
+	}
+
+	@Test
+	public void UITicketOptions() throws Exception {
+		
+		for (TicketModel.Type t : TicketModel.Type.values())
+		{
+			for (TicketModel.Priority p : TicketModel.Priority.values())
+			{
+				for (TicketModel.Severity s : TicketModel.Severity.values())
+				{
+					assertNotNull(service.createTicket(repo, newChange(t, p, s)));
+				}
+			}	
+		}
+	}
+	
+	private Change newChange(Type type, Priority priority, Severity severity) {
+		Change change = new Change("JUnit");
+		change.setField(Field.title, String.format("Type: %s | Priority: %s | Severity: %s", type, priority, severity));
+		change.setField(Field.type, type);
+		change.setField(Field.severity, severity);
+		change.setField(Field.priority, priority);
+		return change;
+	}
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
index 7b56362..8897ef7 100644
--- a/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
+++ b/src/test/java/com/gitblit/tests/mock/MockRuntimeManager.java
@@ -30,6 +30,7 @@
 import com.gitblit.models.SettingModel;
 import com.gitblit.utils.XssFilter;
 import com.gitblit.utils.XssFilter.AllowXssFilter;
+import com.google.inject.Injector;
 
 public class MockRuntimeManager implements IRuntimeManager {
 
@@ -59,6 +60,11 @@
 	}
 
 	@Override
+	public Injector getInjector() {
+		return null;
+	}
+
+	@Override
 	public void setBaseFolder(File folder) {
 		this.baseFolder = folder;
 	}
@@ -76,26 +82,6 @@
 	@Override
 	public Locale getLocale() {
 		return Locale.getDefault();
-	}
-
-	@Override
-	public boolean isServingRepositories() {
-		return true;
-	}
-
-	@Override
-	public boolean isServingHTTP() {
-		return true;
-	}
-
-	@Override
-	public boolean isServingGIT() {
-		return true;
-	}
-
-	@Override
-	public boolean isServingSSH() {
-		return true;
 	}
 
 	@Override

--
Gitblit v1.9.1