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