James Moger
2013-09-30 699e71e76b15081baf746c6ce9c9144f7e5f1ff9
Trim trailing whitespace and organize imports

Change-Id: I9f91138b20219be6e3c4b28251487df262bff6cc
270 files modified
6430 ■■■■ changed files
src/main/java/com/gitblit/AccessRestrictionFilter.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/AddIndexedBranch.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/AuthenticationFilter.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/BranchGraphServlet.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/ConfigUserService.java 123 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Constants.java 132 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DownloadZipFilter.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/DownloadZipServlet.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/EnforceAuthenticationFilter.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationClient.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationPullExecutor.java 20 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FederationServlet.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FileSettings.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GCExecutor.java 52 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlit.java 594 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlitException.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitBlitServer.java 52 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitFilter.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitblitSslContextFactory.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/GitblitTrustManager.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/HtpasswdUserService.java 20 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/IStoredSettings.java 48 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/IUserService.java 109 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/JsonServlet.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/Launcher.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/LdapUserService.java 95 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/LogoServlet.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/LuceneExecutor.java 220 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/MailExecutor.java 23 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/PAMUserService.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/PagesFilter.java 28 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/PagesServlet.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/RedmineUserService.java 23 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/RobotsTxtServlet.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/RpcFilter.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/RpcServlet.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/SalesforceUserService.java 9 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/SparkleShareInviteServlet.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/SyndicationFilter.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/SyndicationServlet.java 32 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/WebXmlSettings.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/WindowsUserService.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/AuthorityWorker.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/CertificateStatusRenderer.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/CertificatesTableModel.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/DefaultOidsPanel.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/GitblitAuthority.java 168 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/Launcher.java 19 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/NewCertificateConfig.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/NewClientCertificateDialog.java 48 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/RequestFocusListener.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/UserCertificateConfig.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/UserCertificateModel.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/UserCertificatePanel.java 79 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/UserCertificateTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/UserOidsPanel.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/Utils.java 27 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/authority/X509CertificateViewer.java 29 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/BooleanCellRenderer.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/BranchRenderer.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/ClosableTabComponent.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/DateCellRenderer.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/EditRegistrationDialog.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/EditRepositoryDialog.java 93 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/EditTeamDialog.java 23 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/EditUserDialog.java 49 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/FeedEntryTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/FeedsPanel.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/FeedsTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/GitblitClient.java 32 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/GitblitManager.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/GitblitManagerLauncher.java 19 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/GitblitPanel.java 19 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/GitblitRegistration.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/GitblitWorker.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/IndicatorsRenderer.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/JPalette.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/MessageRenderer.java 17 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/NameRenderer.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/PropertiesTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java 27 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/RegistrationsDialog.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/RegistrationsTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/RepositoriesPanel.java 25 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/RepositoriesTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SearchDialog.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SettingCellRenderer.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SettingPanel.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SettingsPanel.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SettingsTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/StatusPanel.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/SubscriptionsDialog.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/TeamsPanel.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/TeamsTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/Translation.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/UsersPanel.java 22 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/UsersTableModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/client/Utils.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutClient.java 64 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutConstants.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutNioService.java 43 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutService.java 142 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutServiceConnection.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutSocketService.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/fanout/FanoutStats.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitDaemon.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitDaemonClient.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitDaemonService.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitServlet.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitReceivePack.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/git/RepositoryResolver.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/Activity.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/AnnotatedLine.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/DailyLogEntry.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/FederationModel.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/FederationProposal.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/FederationSet.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/FeedEntryModel.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/FeedModel.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ForkModel.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/GitClientApplication.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/GitNote.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/GravatarProfile.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/Metric.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/PathModel.java 18 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ProjectModel.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RefLogEntry.java 80 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RefModel.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RegistrantAccessPermission.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RepositoryCommit.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/RepositoryModel.java 44 ●●●● 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/SearchResult.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ServerSettings.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ServerStatus.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/SettingModel.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/SubmoduleModel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/TeamModel.java 40 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/UserModel.java 94 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/UserPreferences.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/UserRepositoryPreferences.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ActivityUtils.java 22 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ArrayUtils.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/Base64.java 14 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ByteFormat.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ClientLogger.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/CommitCache.java 68 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/CompressionUtils.java 36 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ConnectionUtils.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ContainerUtils.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/DeepCopier.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/DiffUtils.java 38 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/FederationUtils.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/FileUtils.java 40 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/HttpUtils.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/JGitUtils.java 172 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/JnaUtils.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/JsonUtils.java 32 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/MarkdownUtils.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/MetricUtils.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ModelUtils.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/ObjectCache.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/PatchFormatter.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/RefLogUtils.java 78 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/RpcUtils.java 90 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/StringUtils.java 108 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/SyndicationUtils.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/TimeUtils.java 40 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/utils/X509Utils.java 262 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/AuthorizationStrategy.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/CacheControl.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/ExternalImage.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitBlitWebSession.java 23 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java 16 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitblitRedirectException.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/GitblitWicketFilter.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/PageRegistration.java 30 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/SessionlessForm.java 28 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/StringChoiceRenderer.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/WicketUtils.java 13 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/charting/GoogleChart.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/charting/GoogleCharts.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/charting/GoogleLineChart.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/charting/GooglePieChart.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/charting/SecureChart.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java 11 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/freemarker/Freemarker.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java 26 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/ng/NgController.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ActivityPage.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BasePage.java 44 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BlamePage.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/BlobPage.java 22 ●●●● 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.java 8 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/CommitPage.java 21 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ComparePage.java 31 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DashboardPage.java 16 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/DocsPage.java 3 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java 100 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditTeamPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EditUserPage.java 24 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ForkPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ForksPage.java 25 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/GitSearchPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/HistoryPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/LogPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/LogoutPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java 40 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MarkdownPage.java 6 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MetricsPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java 34 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/OverviewPage.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ProjectPage.java 31 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ProjectsPage.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RawPage.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ReflogPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RootPage.java 39 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/RootSubPage.java 12 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/SendProposalPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/SummaryPage.java 11 ●●●● 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/TagsPage.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/TreePage.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/UserPage.java 21 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/pages/UsersPage.java 4 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ActivityPanel.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/BasePanel.java 12 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/BranchesPanel.java 15 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/BulletListPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/DigestsPanel.java 36 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/DropDownMenu.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java 28 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java 38 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/GravatarImage.java 8 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/HistoryPanel.java 25 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/LinkPanel.java 2 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/LogPanel.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/NavigationPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ObjectContainer.java 4 ●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/PagerPanel.java 7 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ReflogPanel.java 40 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RefsPanel.java 13 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java 45 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java 11 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java 71 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/SearchPanel.java 1 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/ShockWaveComponent.java 10 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/wicket/panels/TagsPanel.java 3 ●●●● 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/UsersPanel.java 3 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java 1 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/JnaUtilsTest.java 10 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/AccessRestrictionFilter.java
@@ -32,22 +32,22 @@
/**
 * The AccessRestrictionFilter is an AuthenticationFilter that confirms that the
 * requested repository can be accessed by the anonymous or named user.
 *
 *
 * The filter extracts the name of the repository from the url and determines if
 * the requested action for the repository requires a Basic authentication
 * prompt. If authentication is required and no credentials are stored in the
 * "Authorization" header, then a basic authentication challenge is issued.
 *
 *
 * http://en.wikipedia.org/wiki/Basic_access_authentication
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class AccessRestrictionFilter extends AuthenticationFilter {
    /**
     * Extract the repository name from the url.
     *
     *
     * @param url
     * @return repository name
     */
@@ -55,7 +55,7 @@
    /**
     * Analyze the url and returns the action of the request.
     *
     *
     * @param url
     * @return action of the request
     */
@@ -63,14 +63,14 @@
    /**
     * Determine if a non-existing repository can be created using this filter.
     *
     *
     * @return true if the filter allows repository creation
     */
    protected abstract boolean isCreationAllowed();
    /**
     * Determine if the action may be executed on the repository.
     *
     *
     * @param repository
     * @param action
     * @return true if the action may be performed
@@ -79,7 +79,7 @@
    /**
     * Determine if the repository requires authentication.
     *
     *
     * @param repository
     * @param action
     * @return true if authentication required
@@ -89,7 +89,7 @@
    /**
     * Determine if the user can access the repository and perform the specified
     * action.
     *
     *
     * @param repository
     * @param user
     * @param action
@@ -99,7 +99,7 @@
    /**
     * Allows a filter to create a repository, if one does not exist.
     *
     *
     * @param user
     * @param repository
     * @param action
@@ -108,11 +108,11 @@
    protected RepositoryModel createRepository(UserModel user, String repository, String action) {
        return null;
    }
    /**
     * 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)
     */
@@ -125,7 +125,7 @@
        String fullUrl = getFullUrl(httpRequest);
        String repository = extractRepositoryName(fullUrl);
        if (GitBlit.self().isCollectingGarbage(repository)) {
            logger.info(MessageFormat.format("ARF: Rejecting request for {0}, busy collecting garbage!", repository));
            httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN);
@@ -155,7 +155,7 @@
                    model = createRepository(user, repository, urlRequestType);
                }
            }
            if (model == null) {
                // repository not found. send 404.
                logger.info(MessageFormat.format("ARF: {0} ({1})", fullUrl,
@@ -164,7 +164,7 @@
                return;
            }
        }
        // Confirm that the action may be executed on the repository
        if (!isActionAllowed(model, urlRequestType)) {
            logger.info(MessageFormat.format("ARF: action {0} on {1} forbidden ({2})",
src/main/java/com/gitblit/AddIndexedBranch.java
@@ -40,9 +40,9 @@
/**
 * Utility class to add an indexBranch setting to matching repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class AddIndexedBranch {
@@ -56,17 +56,17 @@
            jc.usage();
            return;
        }
        // create a lowercase set of excluded repositories
        Set<String> exclusions = new TreeSet<String>();
        for (String exclude : params.exclusions) {
            exclusions.add(exclude.toLowerCase());
        }
        // determine available repositories
        File folder = new File(params.folder);
        List<String> repoList = JGitUtils.getRepositoryList(folder, false, true, -1, null);
        int modCount = 0;
        int skipCount = 0;
        for (String repo : repoList) {
@@ -77,23 +77,23 @@
                    break;
                }
            }
            if (skip) {
                System.out.println("skipping " + repo);
                skipCount++;
                continue;
            }
            try {
                // load repository config
                File gitDir = FileKey.resolve(new File(folder, repo), FS.DETECTED);
                Repository repository = new FileRepositoryBuilder().setGitDir(gitDir).build();
                StoredConfig config = repository.getConfig();
                config.load();
                Set<String> indexedBranches = new LinkedHashSet<String>();
                // add all local branches to index
                if(params.addAllLocalBranches) {
                    List<RefModel> list = JGitUtils.getLocalBranches(repository, true, -1);
@@ -107,7 +107,7 @@
                    System.out.println(MessageFormat.format("adding [gitblit] indexBranch={0} for {1}", params.branch, repo));
                    indexedBranches.add(params.branch);
                }
                String [] branches = config.getStringList("gitblit", null, "indexBranch");
                if (!ArrayUtils.isEmpty(branches)) {
                    for (String branch : branches) {
@@ -122,11 +122,11 @@
                e.printStackTrace();
            }
        }
        System.out.println(MessageFormat.format("updated {0} repository configurations, skipped {1}", modCount, skipCount));
    }
    /**
     * JCommander Parameters class for AddIndexedBranch.
@@ -142,7 +142,7 @@
        @Parameter(names = { "--skip" }, description = "Skip the named repository (simple fizzy matching is supported)", required = false)
        public List<String> exclusions = new ArrayList<String>();
        @Parameter(names = { "--all-local-branches" }, description = "Add all local branches to index. If specified, the --branch parameter is not considered.", required = false)
        public boolean addAllLocalBranches = false;
    }
src/main/java/com/gitblit/AuthenticationFilter.java
@@ -42,11 +42,11 @@
/**
 * 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 {
@@ -59,17 +59,17 @@
    /**
     * 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() {
@@ -78,7 +78,7 @@
    /**
     * Returns the full relative url of the request.
     *
     *
     * @param httpRequest
     * @return url
     */
@@ -95,7 +95,7 @@
    /**
     * Returns the user making the request, if the user has authenticated.
     *
     *
     * @param httpRequest
     * @return user
     */
src/main/java/com/gitblit/BranchGraphServlet.java
@@ -55,9 +55,9 @@
/**
 * Handles requests for branch graphs
 *
 *
 * @author James Moger
 *
 *
 */
public class BranchGraphServlet extends HttpServlet {
@@ -82,7 +82,7 @@
    /**
     * Returns an url to this servlet for the specified parameters.
     *
     *
     * @param baseURL
     * @param repository
     * @param objectId
@@ -148,7 +148,7 @@
            }
            // fetch the requested commits plus some extra so that the last
            // commit displayed *likely* has correct lane assignments
            // commit displayed *likely* has correct lane assignments
            CommitList commitList = new CommitList();
            commitList.source(rw);
            commitList.fillTo(2*Math.max(requestedCommits, maxCommits));
@@ -190,7 +190,7 @@
            // create an image buffer and render the lanes
            BufferedImage image = new BufferedImage(graphWidth, rowHeight*numCommits, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = null;
            try {
                g = image.createGraphics();
src/main/java/com/gitblit/ConfigUserService.java
@@ -45,16 +45,16 @@
/**
 * ConfigUserService is Gitblit's default user service implementation since
 * version 0.8.0.
 *
 *
 * Users and their repository memberships are stored in a git-style config file
 * which is cached and dynamically reloaded when modified. This file is
 * plain-text, human-readable, and may be edited with a text editor.
 *
 *
 * Additionally, this format allows for expansion of the user model without
 * bringing in the complexity of a database.
 *
 *
 * @author James Moger
 *
 *
 */
public class ConfigUserService implements IUserService {
@@ -63,21 +63,21 @@
    private static final String USER = "user";
    private static final String PASSWORD = "password";
    private static final String DISPLAYNAME = "displayName";
    private static final String EMAILADDRESS = "emailAddress";
    private static final String ORGANIZATIONALUNIT = "organizationalUnit";
    private static final String ORGANIZATION = "organization";
    private static final String LOCALITY = "locality";
    private static final String STATEPROVINCE = "stateProvince";
    private static final String COUNTRYCODE = "countryCode";
    private static final String COOKIE = "cookie";
    private static final String REPOSITORY = "repository";
@@ -89,9 +89,9 @@
    private static final String PRERECEIVE = "preReceiveScript";
    private static final String POSTRECEIVE = "postReceiveScript";
    private static final String STARRED = "starred";
    private static final String LOCALE = "locale";
    private final File realmFile;
@@ -105,7 +105,7 @@
    private final Map<String, TeamModel> teams = new ConcurrentHashMap<String, TeamModel>();
    private volatile long lastModified;
    private volatile boolean forceReload;
    public ConfigUserService(File realmFile) {
@@ -114,7 +114,7 @@
    /**
     * Setup the user service.
     *
     *
     * @param settings
     * @since 0.7.0
     */
@@ -124,7 +124,7 @@
    /**
     * Does the user service support changes to credentials?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
@@ -135,7 +135,7 @@
    /**
     * Does the user service support changes to user display name?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
@@ -146,7 +146,7 @@
    /**
     * Does the user service support changes to user email address?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
@@ -157,17 +157,18 @@
    /**
     * Does the user service support changes to team memberships?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
     */
    @Override
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
    /**
     * Does the user service support cookie authentication?
     *
     *
     * @return true or false
     */
    @Override
@@ -177,7 +178,7 @@
    /**
     * Returns the cookie value for the specified user.
     *
     *
     * @param model
     * @return cookie value
     */
@@ -195,7 +196,7 @@
    /**
     * Authenticate a user based on their cookie.
     *
     *
     * @param cookie
     * @return a user object or null
     */
@@ -210,7 +211,7 @@
        if (cookies.containsKey(hash)) {
            model = cookies.get(hash);
        }
        if (model != null) {
            // clone the model, otherwise all changes to this object are
            // live and unpersisted
@@ -221,7 +222,7 @@
    /**
     * Authenticate a user based on a username and password.
     *
     *
     * @param username
     * @param password
     * @return a user object or null
@@ -255,16 +256,16 @@
    /**
     * Logout a user.
     *
     *
     * @param user
     */
    @Override
    public void logout(UserModel user) {
    public void logout(UserModel user) {
    }
    /**
     * Retrieve the user object for the specified username.
     *
     *
     * @param username
     * @return a user object or null
     */
@@ -282,7 +283,7 @@
    /**
     * Updates/writes a complete user object.
     *
     *
     * @param model
     * @return true if update is successful
     */
@@ -293,7 +294,7 @@
    /**
     * Updates/writes all specified user objects.
     *
     *
     * @param models a list of user models
     * @return true if update is successful
     * @since 1.2.0
@@ -317,7 +318,7 @@
                        } else {
                            // do not clobber existing team definition
                            // maybe because this is a federated user
                            t.addUser(model.username);
                            t.addUser(model.username);
                        }
                    }
@@ -343,7 +344,7 @@
    /**
     * Updates/writes and replaces a complete user object keyed by username.
     * This method allows for renaming a user.
     *
     *
     * @param username
     *            the old username
     * @param model
@@ -401,7 +402,7 @@
    /**
     * Deletes the user object from the user service.
     *
     *
     * @param model
     * @return true if successful
     */
@@ -412,7 +413,7 @@
    /**
     * Delete the user object with the specified username
     *
     *
     * @param username
     * @return true if successful
     */
@@ -448,7 +449,7 @@
    /**
     * Returns the list of all teams available to the login service.
     *
     *
     * @return list of all teams
     * @since 0.8.0
     */
@@ -462,7 +463,7 @@
    /**
     * Returns the list of all teams available to the login service.
     *
     *
     * @return list of all teams
     * @since 0.8.0
     */
@@ -478,7 +479,7 @@
    /**
     * Returns the list of all users who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @return list of all usernames that can bypass the access restriction
@@ -504,7 +505,7 @@
    /**
     * Sets the list of all teams who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @param teamnames
@@ -542,7 +543,7 @@
    /**
     * Retrieve the team object for the specified team name.
     *
     *
     * @param teamname
     * @return a team object or null
     * @since 0.8.0
@@ -561,7 +562,7 @@
    /**
     * Updates/writes a complete team object.
     *
     *
     * @param model
     * @return true if update is successful
     * @since 0.8.0
@@ -573,7 +574,7 @@
    /**
     * Updates/writes all specified team objects.
     *
     *
     * @param models a list of team models
     * @return true if update is successful
     * @since 1.2.0
@@ -596,7 +597,7 @@
    /**
     * Updates/writes and replaces a complete team object keyed by teamname.
     * This method allows for renaming a team.
     *
     *
     * @param teamname
     *            the old teamname
     * @param model
@@ -628,7 +629,7 @@
    /**
     * Deletes the team object from the user service.
     *
     *
     * @param model
     * @return true if successful
     * @since 0.8.0
@@ -640,7 +641,7 @@
    /**
     * Delete the team object with the specified teamname
     *
     *
     * @param teamname
     * @return true if successful
     * @since 0.8.0
@@ -661,7 +662,7 @@
    /**
     * Returns the list of all users available to the login service.
     *
     *
     * @return list of all usernames
     */
    @Override
@@ -671,10 +672,10 @@
        Collections.sort(list);
        return list;
    }
    /**
     * Returns the list of all users available to the login service.
     *
     *
     * @return list of all usernames
     */
    @Override
@@ -684,12 +685,12 @@
        list = DeepCopier.copy(list);
        Collections.sort(list);
        return list;
    }
    }
    /**
     * Returns the list of all users who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @return list of all usernames that can bypass the access restriction
@@ -715,7 +716,7 @@
    /**
     * Sets the list of all uses who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @param usernames
@@ -754,7 +755,7 @@
    /**
     * Renames a repository role.
     *
     *
     * @param oldRole
     * @param newRole
     * @return true if successful
@@ -790,7 +791,7 @@
    /**
     * Removes a repository role from all users.
     *
     *
     * @param role
     * @return true if successful
     */
@@ -820,7 +821,7 @@
    /**
     * Writes the properties file.
     *
     *
     * @throws IOException
     */
    private synchronized void write() throws IOException {
@@ -896,7 +897,7 @@
                }
                config.setStringList(USER, model.username, REPOSITORY, permissions);
            }
            // user preferences
            if (model.getPreferences() != null) {
                List<String> starred =  model.getPreferences().getStarredRepositories();
@@ -925,7 +926,7 @@
                roles.add(Constants.NO_ROLE);
            }
            config.setStringList(TEAM, model.name, ROLE, roles);
            if (!model.canAdmin) {
                // write team permission for non-admin teams
                if (model.permissions == null) {
@@ -1015,7 +1016,7 @@
                Set<String> usernames = config.getSubsections(USER);
                for (String username : usernames) {
                    UserModel user = new UserModel(username.toLowerCase());
                    user.password = config.getString(USER, username, PASSWORD);
                    user.password = config.getString(USER, username, PASSWORD);
                    user.displayName = config.getString(USER, username, DISPLAYNAME);
                    user.emailAddress = config.getString(USER, username, EMAILADDRESS);
                    user.organizationalUnit = config.getString(USER, username, ORGANIZATIONALUNIT);
@@ -1024,7 +1025,7 @@
                    user.stateProvince = config.getString(USER, username, STATEPROVINCE);
                    user.countryCode = config.getString(USER, username, COUNTRYCODE);
                    user.cookie = config.getString(USER, username, COOKIE);
                    user.getPreferences().locale = config.getString(USER, username, LOCALE);
                    user.getPreferences().locale = config.getString(USER, username, LOCALE);
                    if (StringUtils.isEmpty(user.cookie) && !StringUtils.isEmpty(user.password)) {
                        user.cookie = StringUtils.getSHA1(user.username + user.password);
                    }
@@ -1071,7 +1072,7 @@
                    team.canAdmin = roles.contains(Constants.ADMIN_ROLE);
                    team.canFork = roles.contains(Constants.FORK_ROLE);
                    team.canCreate = roles.contains(Constants.CREATE_ROLE);
                    if (!team.canAdmin) {
                        // non-admin team, read permissions
                        team.addRepositoryPermissions(Arrays.asList(config.getStringList(TEAM, teamname,
src/main/java/com/gitblit/Constants.java
@@ -26,9 +26,9 @@
/**
 * Constant values used by Gitblit.
 *
 *
 * @author James Moger
 *
 *
 */
public class Constants {
@@ -37,19 +37,19 @@
    public static final String FULL_NAME = "Gitblit - a pure Java Git solution";
    public static final String ADMIN_ROLE = "#admin";
    public static final String FORK_ROLE = "#fork";
    public static final String CREATE_ROLE = "#create";
    public static final String NOT_FEDERATED_ROLE = "#notfederated";
    public static final String NO_ROLE = "#none";
    public static final String EXTERNAL_ACCOUNT = "#externalAccount";
    public static final String PROPERTIES_FILE = "gitblit.properties";
    public static final String DEFAULT_USER_REPOSITORY_PREFIX = "~";
    public static final String GIT_PATH = "/git/";
@@ -61,11 +61,11 @@
    public static final String FEDERATION_PATH = "/federation/";
    public static final String RPC_PATH = "/rpc/";
    public static final String PAGES = "/pages/";
    public static final String SPARKLESHARE_INVITE_PATH = "/sparkleshare/";
    public static final String BRANCH_GRAPH_PATH = "/graph/";
    public static final String BORDER = "***********************************************************";
@@ -73,41 +73,41 @@
    public static final String FEDERATION_USER = "$gitblit";
    public static final String PROPOSAL_EXT = ".json";
    public static final String ENCODING = "UTF-8";
    public static final int LEN_SHORTLOG = 78;
    public static final int LEN_SHORTLOG_REFS = 60;
    public static final String DEFAULT_BRANCH = "default";
    public static final String CONFIG_GITBLIT = "gitblit";
    public static final String CONFIG_CUSTOM_FIELDS = "customFields";
    public static final String ISO8601 = "yyyy-MM-dd'T'HH:mm:ssZ";
    public static final String baseFolder = "baseFolder";
    public static final String baseFolder$ = "${" + baseFolder + "}";
    public static final String contextFolder$ = "${contextFolder}";
    public static final String HEAD = "HEAD";
    public static final String R_GITBLIT = "refs/gitblit/";
    public static final String R_HEADS = "refs/heads/";
    public static final String R_NOTES = "refs/notes/";
    public static final String R_CHANGES = "refs/changes/";
    public static final String R_PULL= "refs/pull/";
    public static final String R_TAGS = "refs/tags/";
    public static final String R_REMOTES = "refs/remotes/";
    public static String getVersion() {
@@ -121,11 +121,11 @@
    public static String getGitBlitVersion() {
        return NAME + " v" + getVersion();
    }
    public static String getBuildDate() {
        return getManifestValue("build-date", "PENDING");
    }
    private static String getManifestValue(String attrib, String defaultValue) {
        Class<?> clazz = Constants.class;
        String className = clazz.getSimpleName() + ".class";
@@ -144,13 +144,13 @@
        }
        return defaultValue;
    }
    /**
     * Enumeration representing the four access restriction levels.
     */
    public static enum AccessRestrictionType {
        NONE, PUSH, CLONE, VIEW;
        private static final AccessRestrictionType [] AUTH_TYPES = { PUSH, CLONE, VIEW };
        public static AccessRestrictionType fromName(String name) {
@@ -161,7 +161,7 @@
            }
            return NONE;
        }
        public static List<AccessRestrictionType> choices(boolean allowAnonymousPush) {
            if (allowAnonymousPush) {
                return Arrays.asList(values());
@@ -177,10 +177,11 @@
            return this.ordinal() >= type.ordinal();
        }
        @Override
        public String toString() {
            return name();
        }
        public boolean isValidPermission(AccessPermission permission) {
            switch (this) {
            case VIEW:
@@ -193,7 +194,7 @@
                return permission.atLeast(AccessPermission.CLONE);
            case PUSH:
                // PUSH restriction
                // only PUSH or greater access permissions are valid
                // only PUSH or greater access permissions are valid
                return permission.atLeast(AccessPermission.PUSH);
            case NONE:
                // NO access restriction
@@ -203,14 +204,14 @@
            return false;
        }
    }
    /**
     * Enumeration representing the types of authorization control for an
     * access restricted resource.
     */
    public static enum AuthorizationControl {
        AUTHENTICATED, NAMED;
        public static AuthorizationControl fromName(String name) {
            for (AuthorizationControl type : values()) {
                if (type.name().equalsIgnoreCase(name)) {
@@ -219,7 +220,8 @@
            }
            return NAMED;
        }
        @Override
        public String toString() {
            return name();
        }
@@ -241,6 +243,7 @@
            return REPOSITORIES;
        }
        @Override
        public String toString() {
            return name();
        }
@@ -261,6 +264,7 @@
            return PULL_REPOSITORIES;
        }
        @Override
        public String toString() {
            return name();
        }
@@ -337,11 +341,11 @@
        // Order is important here.  anything above LIST_SETTINGS requires
        // administrator privileges and web.allowRpcManagement.
        CLEAR_REPOSITORY_CACHE, GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, GET_USER, LIST_SETTINGS,
        CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY,
        LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER,
        CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY,
        LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER,
        LIST_TEAMS, CREATE_TEAM, EDIT_TEAM, DELETE_TEAM,
        LIST_REPOSITORY_MEMBERS, SET_REPOSITORY_MEMBERS, LIST_REPOSITORY_TEAMS, SET_REPOSITORY_TEAMS,
        LIST_REPOSITORY_MEMBER_PERMISSIONS, SET_REPOSITORY_MEMBER_PERMISSIONS, LIST_REPOSITORY_TEAM_PERMISSIONS, SET_REPOSITORY_TEAM_PERMISSIONS,
        LIST_REPOSITORY_MEMBERS, SET_REPOSITORY_MEMBERS, LIST_REPOSITORY_TEAMS, SET_REPOSITORY_TEAMS,
        LIST_REPOSITORY_MEMBER_PERMISSIONS, SET_REPOSITORY_MEMBER_PERMISSIONS, LIST_REPOSITORY_TEAM_PERMISSIONS, SET_REPOSITORY_TEAM_PERMISSIONS,
        LIST_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS,
        EDIT_SETTINGS, LIST_STATUS;
@@ -352,7 +356,7 @@
                }
            }
            return null;
        }
        }
        public boolean exceeds(RpcRequest type) {
            return this.ordinal() > type.ordinal();
@@ -369,7 +373,7 @@
     */
    public static enum SearchType {
        AUTHOR, COMMITTER, COMMIT;
        public static SearchType forName(String name) {
            for (SearchType type : values()) {
                if (type.name().equalsIgnoreCase(name)) {
@@ -378,13 +382,13 @@
            }
            return COMMIT;
        }
        @Override
        public String toString() {
            return name().toLowerCase();
        }
    }
    /**
     * The types of objects that can be indexed and queried.
     */
@@ -400,19 +404,19 @@
            return null;
        }
    }
    /**
     * The access permissions available for a repository.
     * The access permissions available for a repository.
     */
    public static enum AccessPermission {
        NONE("N"), EXCLUDE("X"), VIEW("V"), CLONE("R"), PUSH("RW"), CREATE("RWC"), DELETE("RWD"), REWIND("RW+"), OWNER("RW+");
        public static final AccessPermission [] NEWPERMISSIONS = { EXCLUDE, VIEW, CLONE, PUSH, CREATE, DELETE, REWIND };
        public static AccessPermission LEGACY = REWIND;
        public final String code;
        private AccessPermission(String code) {
            this.code = code;
        }
@@ -428,16 +432,16 @@
        public boolean exceeds(AccessPermission perm) {
            return ordinal() > perm.ordinal();
        }
        public String asRole(String repository) {
            return code + ":" + repository;
        }
        @Override
        public String toString() {
            return code;
        }
        public static AccessPermission permissionFromRole(String role) {
            String [] fields = role.split(":", 2);
            if (fields.length == 1) {
@@ -448,7 +452,7 @@
                return AccessPermission.fromCode(fields[0]);
            }
        }
        public static String repositoryFromRole(String role) {
            String [] fields = role.split(":", 2);
            if (fields.length == 1) {
@@ -459,7 +463,7 @@
                return fields[1];
            }
        }
        public static AccessPermission fromCode(String code) {
            for (AccessPermission perm : values()) {
                if (perm.code.equalsIgnoreCase(code)) {
@@ -469,18 +473,18 @@
            return AccessPermission.NONE;
        }
    }
    public static enum RegistrantType {
        REPOSITORY, USER, TEAM;
    }
    public static enum PermissionType {
        MISSING, ANONYMOUS, EXPLICIT, TEAM, REGEX, OWNER, ADMINISTRATOR;
    }
    public static enum GCStatus {
        READY, COLLECTING;
        public boolean exceeds(GCStatus s) {
            return ordinal() > s.ordinal();
        }
@@ -488,23 +492,23 @@
    public static enum AuthenticationType {
        CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;
        public boolean isStandard() {
            return ordinal() <= COOKIE.ordinal();
        }
    }
    public static enum AccountType {
        LOCAL, EXTERNAL, LDAP, REDMINE, SALESFORCE, WINDOWS, PAM, HTPASSWD;
        public boolean isLocal() {
            return this == LOCAL;
        }
    }
    public static enum CommitMessageRenderer {
        PLAIN, MARKDOWN;
        public static CommitMessageRenderer fromName(String name) {
            for (CommitMessageRenderer renderer : values()) {
                if (renderer.name().equalsIgnoreCase(name)) {
src/main/java/com/gitblit/DownloadZipFilter.java
@@ -23,15 +23,15 @@
 * The DownloadZipFilter is an AccessRestrictionFilter which ensures that zip
 * requests for view-restricted repositories have proper authentication
 * credentials and are authorized.
 *
 *
 * @author James Moger
 *
 *
 */
public class DownloadZipFilter extends AccessRestrictionFilter {
    /**
     * Extract the repository name from the url.
     *
     *
     * @param url
     * @return repository name
     */
@@ -47,7 +47,7 @@
    /**
     * Analyze the url and returns the action of the request.
     *
     *
     * @param url
     * @return action of the request
     */
@@ -58,7 +58,7 @@
    /**
     * Determine if a non-existing repository can be created using this filter.
     *
     *
     * @return true if the filter allows repository creation
     */
    @Override
@@ -68,7 +68,7 @@
    /**
     * Determine if the action may be executed on the repository.
     *
     *
     * @param repository
     * @param action
     * @return true if the action may be performed
@@ -80,7 +80,7 @@
    /**
     * Determine if the repository requires authentication.
     *
     *
     * @param repository
     * @param action
     * @return true if authentication required
@@ -93,7 +93,7 @@
    /**
     * Determine if the user can access the repository and perform the specified
     * action.
     *
     *
     * @param repository
     * @param user
     * @param action
src/main/java/com/gitblit/DownloadZipServlet.java
@@ -37,25 +37,25 @@
/**
 * Streams out a zip file from the specified repository for any tree path at any
 * revision.
 *
 *
 * @author James Moger
 *
 *
 */
public class DownloadZipServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private transient Logger logger = LoggerFactory.getLogger(DownloadZipServlet.class);
    public static enum Format {
        zip(".zip"), tar(".tar"), gz(".tar.gz"), xz(".tar.xz"), bzip2(".tar.bzip2");
        public final String extension;
        Format(String ext) {
            this.extension = ext;
        }
        public static Format fromName(String name) {
            for (Format format : values()) {
                if (format.name().equalsIgnoreCase(name)) {
@@ -72,7 +72,7 @@
    /**
     * Returns an url to this servlet for the specified parameters.
     *
     *
     * @param baseURL
     * @param repository
     * @param objectId
@@ -92,7 +92,7 @@
    /**
     * Creates a zip stream from the repository of the requested data.
     *
     *
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
@@ -106,7 +106,7 @@
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return;
        }
        Format format = Format.zip;
        String repository = request.getParameter("r");
        String basePath = request.getParameter("p");
@@ -115,7 +115,7 @@
        if (!StringUtils.isEmpty(f)) {
            format = Format.fromName(f);
        }
        try {
            String name = repository;
            if (name.indexOf('/') > -1) {
@@ -129,7 +129,7 @@
            if (!StringUtils.isEmpty(objectId)) {
                name += "-" + objectId;
            }
            Repository r = GitBlit.self().getRepository(repository);
            if (r == null) {
                if (GitBlit.self().isCollectingGarbage(repository)) {
@@ -174,14 +174,14 @@
                    CompressionUtils.bzip2(r, basePath, objectId, response.getOutputStream());
                    break;
                }
                response.flushBuffer();
            } catch (IOException t) {
                String message = t.getMessage() == null ? "" : t.getMessage().toLowerCase();
                if (message.contains("reset") || message.contains("broken pipe")) {
                    logger.error("Client aborted zip download: " + message);
                } else {
                    logger.error("Failed to write attachment to client", t);
                    logger.error("Failed to write attachment to client", t);
                }
            } catch (Throwable t) {
                logger.error("Failed to write attachment to client", t);
src/main/java/com/gitblit/EnforceAuthenticationFilter.java
@@ -41,10 +41,10 @@
 *
 */
public class EnforceAuthenticationFilter implements Filter {
    protected transient Logger logger = LoggerFactory.getLogger(getClass());
    /*
    /*
     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
     */
    @Override
@@ -52,27 +52,27 @@
        // nothing to be done
    } //init
    /*
    /*
     * This does the actual filtering: is the user authenticated? If not, enforce HTTP authentication (401)
     *
     *
     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        /*
         * Determine whether to enforce the BASIC authentication:
         */
        @SuppressWarnings("static-access")
        Boolean mustForceAuth = GitBlit.self().getBoolean(Keys.web.authenticateViewPages, false)
                                && GitBlit.self().getBoolean(Keys.web.enforceHttpBasicAuthentication, false);
        HttpServletRequest  HttpRequest  = (HttpServletRequest)request;
        HttpServletResponse HttpResponse = (HttpServletResponse)response;
        HttpServletResponse HttpResponse = (HttpServletResponse)response;
        UserModel user = GitBlit.self().authenticate(HttpRequest);
        if (mustForceAuth && (user == null)) {
            // not authenticated, enforce now:
            logger.debug(MessageFormat.format("EnforceAuthFilter: user not authenticated for URL {0}!", request.toString()));
@@ -85,12 +85,12 @@
        } else {
            // user is authenticated, or don't care, continue handling
            chain.doFilter( request, response );
        } // authenticated
    } // doFilter
    /*
    /*
     * @see javax.servlet.Filter#destroy()
     */
    @Override
src/main/java/com/gitblit/FederationClient.java
@@ -29,9 +29,9 @@
/**
 * Command-line client to pull federated Gitblit repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class FederationClient {
@@ -75,7 +75,7 @@
            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(
src/main/java/com/gitblit/FederationPullExecutor.java
@@ -74,7 +74,7 @@
    /**
     * Constructor for specifying a single federation registration. This
     * constructor is used to schedule the next pull execution.
     *
     *
     * @param registration
     */
    private FederationPullExecutor(FederationModel registration) {
@@ -85,7 +85,7 @@
     * Constructor to specify a group of federation registrations. This is
     * normally used at startup to pull and then schedule the next update based
     * on each registrations frequency setting.
     *
     *
     * @param registrations
     * @param isDaemon
     *            if true, registrations are rescheduled in perpetuity. if
@@ -137,7 +137,7 @@
    /**
     * Mirrors a repository and, optionally, the server's users, and/or
     * configuration settings from a origin Gitblit instance.
     *
     *
     * @param registration
     * @throws Exception
     */
@@ -189,12 +189,12 @@
                            repositoryName.indexOf(DOT_GIT_EXT));
                }
            }
            // confirm that the origin of any pre-existing repository matches
            // the clone url
            String fetchHead = null;
            Repository existingRepository = GitBlit.self().getRepository(repositoryName);
            if (existingRepository == null && GitBlit.self().isCollectingGarbage(repositoryName)) {
                logger.warn(MessageFormat.format("Skipping local repository {0}, busy collecting garbage", repositoryName));
                continue;
@@ -253,13 +253,13 @@
                                String branch = org.eclipse.jgit.lib.Constants.R_HEADS
                                        + ref.displayName.substring(ref.displayName.indexOf('/') + 1);
                                String hash = ref.getReferencedObjectId().getName();
                                JGitUtils.setBranchRef(r, branch, hash);
                                logger.info(MessageFormat.format("     resetting {0} of {1} to {2}", branch,
                                        repository.name, hash));
                            }
                        }
                        String newHead;
                        if (StringUtils.isEmpty(repository.HEAD)) {
                            newHead = newFetchHead;
@@ -298,7 +298,7 @@
                    federationSets.addAll(repository.federationSets);
                }
                repository.federationSets = new ArrayList<String>(federationSets);
                // merge indexed branches
                Set<String> indexedBranches = new HashSet<String>();
                if (rm.indexedBranches != null) {
@@ -487,7 +487,7 @@
    /**
     * Sends a status acknowledgment to the origin Gitblit instance. This
     * includes the results of the federated pull.
     *
     *
     * @param registration
     * @throws Exception
     */
@@ -507,7 +507,7 @@
    /**
     * Schedules the next check of the federated Gitblit instance.
     *
     *
     * @param registration
     */
    private void schedule(FederationModel registration) {
src/main/java/com/gitblit/FederationServlet.java
@@ -40,9 +40,9 @@
/**
 * Handles federation requests.
 *
 *
 * @author James Moger
 *
 *
 */
public class FederationServlet extends JsonServlet {
@@ -54,7 +54,7 @@
    /**
     * Processes a federation request.
     *
     *
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
@@ -231,7 +231,7 @@
                    return;
                }
                Map<String, String> scripts = new HashMap<String, String>();
                Set<String> names = new HashSet<String>();
                names.addAll(GitBlit.getStrings(Keys.groovy.preReceiveScripts));
                names.addAll(GitBlit.getStrings(Keys.groovy.postReceiveScripts));
src/main/java/com/gitblit/FileSettings.java
@@ -26,9 +26,9 @@
/**
 * Dynamically loads and reloads a properties file by keeping track of the last
 * modification date.
 *
 *
 * @author James Moger
 *
 *
 */
public class FileSettings extends IStoredSettings {
@@ -37,7 +37,7 @@
    private final Properties properties = new Properties();
    private volatile long lastModified;
    private volatile boolean forceReload;
    public FileSettings(String file) {
@@ -83,6 +83,7 @@
    /**
     * Updates the specified settings in the settings file.
     */
    @Override
    public synchronized boolean saveSettings(Map<String, String> settings) {
        String content = FileUtils.readContent(propertiesFile, "\n");
        for (Map.Entry<String, String> setting:settings.entrySet()) {
@@ -98,11 +99,11 @@
        }
        FileUtils.writeContent(propertiesFile, content);
        // manually set the forceReload flag because not all JVMs support real
        // millisecond resolution of lastModified. (issue-55)
        // millisecond resolution of lastModified. (issue-55)
        forceReload = true;
        return true;
    }
    private String regExEscape(String input) {
        return input.replace(".", "\\.").replace("$", "\\$").replace("{", "\\{");
    }
src/main/java/com/gitblit/GCExecutor.java
@@ -36,15 +36,15 @@
/**
 * The GC executor handles periodic garbage collection in repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class GCExecutor implements Runnable {
    public static enum GCStatus {
        READY, COLLECTING;
        public boolean exceeds(GCStatus s) {
            return ordinal() > s.ordinal();
        }
@@ -52,11 +52,11 @@
    private final Logger logger = LoggerFactory.getLogger(GCExecutor.class);
    private final IStoredSettings settings;
    private AtomicBoolean running = new AtomicBoolean(false);
    private AtomicBoolean forceClose = new AtomicBoolean(false);
    private final Map<String, GCStatus> gcCache = new ConcurrentHashMap<String, GCStatus>();
    public GCExecutor(IStoredSettings settings) {
@@ -65,24 +65,24 @@
    /**
     * Indicates if the GC executor is ready to process repositories.
     *
     *
     * @return true if the GC executor is ready to process repositories
     */
    public boolean isReady() {
        return settings.getBoolean(Keys.git.enableGarbageCollection, false);
    }
    public boolean isRunning() {
        return running.get();
    }
    public boolean lock(String repositoryName) {
        return setGCStatus(repositoryName, GCStatus.COLLECTING);
    }
    /**
     * Tries to set a GCStatus for the specified repository.
     *
     *
     * @param repositoryName
     * @return true if the status has been set
     */
@@ -100,7 +100,7 @@
    /**
     * Returns true if Gitblit is actively collecting garbage in this repository.
     *
     *
     * @param repositoryName
     * @return true if actively collecting garbage
     */
@@ -111,13 +111,13 @@
    /**
     * Resets the GC status to ready.
     *
     *
     * @param repositoryName
     */
    public void releaseLock(String repositoryName) {
        gcCache.put(repositoryName.toLowerCase(), GCStatus.READY);
    }
    public void close() {
        forceClose.set(true);
    }
@@ -127,8 +127,8 @@
        if (!isReady()) {
            return;
        }
        running.set(true);
        running.set(true);
        Date now = new Date();
        for (String repositoryName : GitBlit.self().getRepositoryList()) {
@@ -149,7 +149,7 @@
                    logger.warn(MessageFormat.format("GCExecutor is missing repository {0}?!?", repositoryName));
                    continue;
                }
                if (!isRepositoryIdle(repository)) {
                    logger.debug(MessageFormat.format("GCExecutor is skipping {0} because it is not idle", repositoryName));
                    continue;
@@ -162,13 +162,13 @@
                    logger.warn(MessageFormat.format("Can not acquire GC lock for {0}, skipping", repositoryName));
                    continue;
                }
                logger.debug(MessageFormat.format("GCExecutor locked idle repository {0}", repositoryName));
                Git git = new Git(repository);
                GarbageCollectCommand gc = git.gc();
                Properties stats = gc.getStatistics();
                // determine if this is a scheduled GC
                Calendar cal = Calendar.getInstance();
                cal.setTime(model.lastGC);
@@ -190,10 +190,10 @@
                if (hasGarbage && (hasEnoughGarbage || shouldCollectGarbage)) {
                    long looseKB = sizeOfLooseObjects/1024L;
                    logger.info(MessageFormat.format("Collecting {1} KB of loose objects from {0}", repositoryName, looseKB));
                    // do the deed
                    gc.call();
                    garbageCollected = true;
                }
            } catch (Exception e) {
@@ -206,19 +206,19 @@
                        model.lastGC = new Date();
                        GitBlit.self().updateConfiguration(repository, model);
                    }
                    repository.close();
                }
                // reset the GC lock
                // reset the GC lock
                releaseLock(repositoryName);
                logger.debug(MessageFormat.format("GCExecutor released GC lock for {0}", repositoryName));
            }
        }
        running.set(false);
    }
    private boolean isRepositoryIdle(Repository repository) {
        try {
            // Read the use count.
src/main/java/com/gitblit/GitBlit.java
@@ -145,28 +145,28 @@
 * the web ui and the servlets. This class is either directly instantiated by
 * the GitBlitServer class (Gitblit GO) or is reflectively instantiated from the
 * definition in the web.xml file (Gitblit WAR).
 *
 *
 * This class is the central logic processor for Gitblit. All settings, user
 * object, and repository object operations pass through this class.
 *
 *
 * Repository Resolution. There are two pathways for finding repositories. One
 * pathway, for web ui display and repository authentication & authorization, is
 * within this class. The other pathway is through the standard GitServlet.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitBlit implements ServletContextListener {
    private static GitBlit gitblit;
    private final Logger logger = LoggerFactory.getLogger(GitBlit.class);
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(5);
    private final List<FederationModel> federationRegistrations = Collections
            .synchronizedList(new ArrayList<FederationModel>());
    private final ObjectCache<Collection<GitClientApplication>> clientApplications = new ObjectCache<Collection<GitClientApplication>>();
    private final Map<String, FederationModel> federationPullResults = new ConcurrentHashMap<String, FederationModel>();
@@ -174,19 +174,19 @@
    private final ObjectCache<Long> repositorySizeCache = new ObjectCache<Long>();
    private final ObjectCache<List<Metric>> repositoryMetricsCache = new ObjectCache<List<Metric>>();
    private final Map<String, RepositoryModel> repositoryListCache = new ConcurrentHashMap<String, RepositoryModel>();
    private final Map<String, ProjectModel> projectCache = new ConcurrentHashMap<String, ProjectModel>();
    private final AtomicReference<String> repositoryListSettingsChecksum = new AtomicReference<String>("");
    private final ObjectCache<String> projectMarkdownCache = new ObjectCache<String>();
    private final ObjectCache<String> projectRepositoriesMarkdownCache = new ObjectCache<String>();
    private ServletContext servletContext;
    private File baseFolder;
    private File repositoriesFolder;
@@ -200,15 +200,15 @@
    private ServerStatus serverStatus;
    private MailExecutor mailExecutor;
    private LuceneExecutor luceneExecutor;
    private GCExecutor gcExecutor;
    private TimeZone timezone;
    private FileBasedConfig projectConfigs;
    private FanoutService fanoutService;
    private GitDaemon gitDaemon;
@@ -227,7 +227,7 @@
    /**
     * Returns the Gitblit singleton.
     *
     *
     * @return gitblit singleton
     */
    public static GitBlit self() {
@@ -236,19 +236,19 @@
        }
        return gitblit;
    }
    /**
     * Returns the boot date of the Gitblit server.
     *
     *
     * @return the boot date of Gitblit
     */
    public static Date getBootDate() {
        return self().serverStatus.bootDate;
    }
    /**
     * Returns the most recent change date of any repository served by Gitblit.
     *
     *
     * @return a date
     */
    public static Date getLastActivityDate() {
@@ -266,17 +266,17 @@
    /**
     * Determine if this is the GO variant of Gitblit.
     *
     *
     * @return true if this is the GO variant of Gitblit.
     */
    public static boolean isGO() {
        return self().settings instanceof FileSettings;
    }
    /**
     * 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
     */
    public static boolean isServingRepositories() {
@@ -286,7 +286,7 @@
    /**
     * 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
     */
    public static boolean isSendingMail() {
@@ -295,7 +295,7 @@
    /**
     * Returns the preferred timezone for the Gitblit instance.
     *
     *
     * @return a timezone
     */
    public static TimeZone getTimezone() {
@@ -309,31 +309,31 @@
        }
        return self().timezone;
    }
    /**
     * Returns the active settings.
     *
     *
     * @return the active settings
     */
    public static IStoredSettings getSettings() {
        return self().settings;
    }
    /**
     * Returns the user-defined blob encodings.
     *
     *
     * @return an array of encodings, may be empty
     */
    public static String [] getEncodings() {
        return getStrings(Keys.web.blobEncodings).toArray(new String[0]);
    }
    /**
     * Returns the boolean value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as a boolean, the
     * defaultValue is returned.
     *
     *
     * @see IStoredSettings.getBoolean(String, boolean)
     * @param key
     * @param defaultValue
@@ -347,7 +347,7 @@
     * Returns the integer value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an integer, the
     * defaultValue is returned.
     *
     *
     * @see IStoredSettings.getInteger(String key, int defaultValue)
     * @param key
     * @param defaultValue
@@ -361,7 +361,7 @@
     * Returns the integer list for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an integer, an
     * empty list is returned.
     *
     *
     * @see IStoredSettings.getIntegers(String key)
     * @param key
     * @return key value or defaultValue
@@ -369,12 +369,12 @@
    public static List<Integer> getIntegers(String key) {
        return self().settings.getIntegers(key);
    }
    /**
     * Returns the value in bytes for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an integer, the
     * defaultValue is returned.
     *
     *
     * @see IStoredSettings.getFilesize(String key, int defaultValue)
     * @param key
     * @param defaultValue
@@ -388,7 +388,7 @@
     * Returns the value in bytes for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as a long, the
     * defaultValue is returned.
     *
     *
     * @see IStoredSettings.getFilesize(String key, long defaultValue)
     * @param key
     * @param defaultValue
@@ -402,7 +402,7 @@
     * Returns the char value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a character, the
     * defaultValue is returned.
     *
     *
     * @see IStoredSettings.getChar(String key, char defaultValue)
     * @param key
     * @param defaultValue
@@ -416,7 +416,7 @@
     * Returns the string value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a string, the
     * defaultValue is returned.
     *
     *
     * @see IStoredSettings.getString(String key, String defaultValue)
     * @param key
     * @param defaultValue
@@ -428,7 +428,7 @@
    /**
     * Returns a list of space-separated strings from the specified key.
     *
     *
     * @see IStoredSettings.getStrings(String key)
     * @param n
     * @return list of strings
@@ -439,7 +439,7 @@
    /**
     * Returns a map of space-separated key-value pairs from the specified key.
     *
     *
     * @see IStoredSettings.getStrings(String key)
     * @param n
     * @return map of string, string
@@ -451,7 +451,7 @@
    /**
     * Returns the list of keys whose name starts with the specified prefix. If
     * the prefix is null or empty, all key names are returned.
     *
     *
     * @see IStoredSettings.getAllKeys(String key)
     * @param startingWith
     * @return list of keys
@@ -463,7 +463,7 @@
    /**
     * Is Gitblit running in debug mode?
     *
     *
     * @return true if Gitblit is running in debug mode
     */
    public static boolean isDebugMode() {
@@ -472,7 +472,7 @@
    /**
     * Returns the file object for the specified configuration key.
     *
     *
     * @return the file
     */
    public static File getFileOrFolder(String key, String defaultFileOrFolder) {
@@ -486,7 +486,7 @@
     * file or folder retrievals are (at least initially) funneled through this
     * method so it is the correct point to globally override/alter filesystem
     * access based on environment or some other indicator.
     *
     *
     * @return the file
     */
    public static File getFileOrFolder(String fileOrFolder) {
@@ -497,7 +497,7 @@
    /**
     * Returns the path of the repositories folder. This method checks to see if
     * Gitblit is running on a cloud service and may return an adjusted path.
     *
     *
     * @return the repositories folder path
     */
    public static File getRepositoriesFolder() {
@@ -507,7 +507,7 @@
    /**
     * Returns the path of the proposals folder. This method checks to see if
     * Gitblit is running on a cloud service and may return an adjusted path.
     *
     *
     * @return the proposals folder path
     */
    public static File getProposalsFolder() {
@@ -517,16 +517,16 @@
    /**
     * Returns the path of the Groovy folder. This method checks to see if
     * Gitblit is running on a cloud service and may return an adjusted path.
     *
     *
     * @return the Groovy scripts folder path
     */
    public static File getGroovyScriptsFolder() {
        return getFileOrFolder(Keys.groovy.scriptsFolder, "${baseFolder}/groovy");
    }
    /**
     * Updates the list of server settings.
     *
     *
     * @param settings
     * @return true if the update succeeded
     */
@@ -540,10 +540,10 @@
        serverStatus.heapFree = Runtime.getRuntime().freeMemory();
        return serverStatus;
    }
    /**
     * Returns a list of repository URLs and the user access permission.
     *
     *
     * @param request
     * @param user
     * @param repository
@@ -589,13 +589,13 @@
        }
        return list;
    }
    protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
        StringBuilder sb = new StringBuilder();
        sb.append(HttpUtils.getGitblitURL(request));
        sb.append(Constants.GIT_PATH);
        sb.append(repository.name);
        // inject username into repository url if authentication is required
        if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
                && !StringUtils.isEmpty(username)) {
@@ -603,7 +603,7 @@
        }
        return sb.toString();
    }
    protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
        if (gitDaemon != null) {
            String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
@@ -620,7 +620,7 @@
        }
        return null;
    }
    protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
        if (gitDaemon != null && user.canClone(repository)) {
            AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
@@ -643,7 +643,7 @@
    /**
     * Returns the list of custom client applications to be used for the
     * repository url panel;
     *
     *
     * @return a collection of client applications
     */
    public Collection<GitClientApplication> getClientApplications() {
@@ -662,13 +662,13 @@
                    if (clients != null) {
                        clientApplications.updateObject("user", lastModified, clients);
                        return clients;
                    }
                    }
                } catch (IOException e) {
                    logger.error("Failed to deserialize " + userDefs.getAbsolutePath(), e);
                }
            }
        }
        // no user definitions, use system definitions
        if (!clientApplications.hasCurrent("system", new Date(0))) {
            try {
@@ -682,10 +682,10 @@
                logger.error("Failed to deserialize clientapps.json resource!", e);
            }
        }
        return clientApplications.getObject("system");
    }
    private Collection<GitClientApplication> readClientApplications(InputStream is) {
        try {
            Type type = new TypeToken<Collection<GitClientApplication>>() {
@@ -705,7 +705,7 @@
    /**
     * Set the user service. The user service authenticates all users and is
     * responsible for managing user permissions.
     *
     *
     * @param userService
     */
    public void setUserService(IUserService userService) {
@@ -713,14 +713,14 @@
        this.userService = userService;
        this.userService.setup(settings);
    }
    public boolean supportsAddUser() {
        return supportsCredentialChanges(new UserModel(""));
    }
    /**
     * Returns true if the user's credentials can be changed.
     *
     *
     * @param user
     * @return true if the user service supports credential changes
     */
@@ -738,7 +738,7 @@
    /**
     * Returns true if the user's display name can be changed.
     *
     *
     * @param user
     * @return true if the user service supports display name changes
     */
@@ -748,7 +748,7 @@
    /**
     * Returns true if the user's email address can be changed.
     *
     *
     * @param user
     * @return true if the user service supports email address changes
     */
@@ -758,7 +758,7 @@
    /**
     * Returns true if the user's team memberships can be changed.
     *
     *
     * @param user
     * @return true if the user service supports team membership changes
     */
@@ -768,7 +768,7 @@
    /**
     * Returns true if the username represents an internal account
     *
     *
     * @param username
     * @return true if the specified username represents an internal account
     */
@@ -780,7 +780,7 @@
    /**
     * Authenticate a user based on a username and password.
     *
     *
     * @see IUserService.authenticate(String, char[])
     * @param username
     * @param password
@@ -817,7 +817,7 @@
    /**
     * Authenticate a user based on their cookie.
     *
     *
     * @param cookies
     * @return a user object or null
     */
@@ -840,22 +840,22 @@
    /**
     * Authenticate a user based on HTTP request parameters.
     *
     *
     * Authentication by X509Certificate is tried first and then by cookie.
     *
     *
     * @param httpRequest
     * @return a user object or null
     */
    public UserModel authenticate(HttpServletRequest httpRequest) {
        return authenticate(httpRequest, false);
    }
    /**
     * Authenticate a user based on HTTP request parameters.
     *
     *
     * Authentication by X509Certificate, servlet container principal, cookie,
     * and BASIC header.
     *
     *
     * @param httpRequest
     * @param requiresCertificate
     * @return a user object or null
@@ -879,12 +879,12 @@
                        model.username, metadata.serialNumber, httpRequest.getRemoteAddr()));
            }
        }
        if (requiresCertificate) {
            // caller requires client certificate authentication (e.g. git servlet)
            return null;
        }
        // try to authenticate by servlet container principal
        Principal principal = httpRequest.getUserPrincipal();
        if (principal != null) {
@@ -915,7 +915,7 @@
                }
            }
        }
        // try to authenticate by cookie
        if (allowCookieAuthentication()) {
            UserModel user = authenticate(httpRequest.getCookies());
@@ -926,7 +926,7 @@
                return user;
            }
        }
        // try to authenticate by BASIC
        final String authorization = httpRequest.getHeader("Authorization");
        if (authorization != null && authorization.startsWith("Basic")) {
@@ -947,14 +947,14 @@
                            user.username, httpRequest.getRemoteAddr()));
                    return user;
                } else {
                    logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
                    logger.warn(MessageFormat.format("Failed login attempt for {0}, invalid credentials from {1}",
                            username, httpRequest.getRemoteAddr()));
                }
            }
        }
        return null;
    }
    protected void flagWicketSession(AuthenticationType authenticationType) {
        RequestCycle requestCycle = RequestCycle.get();
        if (requestCycle != null) {
@@ -977,7 +977,7 @@
    /**
     * Sets a cookie for the specified user.
     *
     *
     * @param response
     * @param user
     */
@@ -1009,10 +1009,10 @@
            response.addCookie(userCookie);
        }
    }
    /**
     * Logout a user.
     *
     *
     * @param user
     */
    public void logout(UserModel user) {
@@ -1024,27 +1024,27 @@
    /**
     * Encode the username for user in an url.
     *
     *
     * @param name
     * @return the encoded name
     */
    protected String encodeUsername(String name) {
        return name.replace("@", "%40").replace(" ", "%20").replace("\\", "%5C");
        return name.replace("@", "%40").replace(" ", "%20").replace("\\", "%5C");
    }
    /**
     * Decode a username from an encoded url.
     *
     *
     * @param name
     * @return the decoded name
     */
    protected String decodeUsername(String name) {
        return name.replace("%40", "@").replace("%20", " ").replace("%5C", "\\");
    }
    /**
     * Returns the list of all users available to the login service.
     *
     *
     * @see IUserService.getAllUsernames()
     * @return list of all usernames
     */
@@ -1055,7 +1055,7 @@
    /**
     * Returns the list of all users available to the login service.
     *
     *
     * @see IUserService.getAllUsernames()
     * @return list of all usernames
     */
@@ -1066,7 +1066,7 @@
    /**
     * Delete the user object with the specified username
     *
     *
     * @see IUserService.deleteUser(String)
     * @param username
     * @return true if successful
@@ -1078,7 +1078,7 @@
        String usernameDecoded = decodeUsername(username);
        return userService.deleteUser(usernameDecoded);
    }
    protected UserModel getFederationUser() {
        // the federation user is an administrator
        UserModel federationUser = new UserModel(Constants.FEDERATION_USER);
@@ -1088,7 +1088,7 @@
    /**
     * Retrieve the user object for the specified username.
     *
     *
     * @see IUserService.getUserModel(String)
     * @param username
     * @return a user object or null
@@ -1098,14 +1098,14 @@
            return null;
        }
        String usernameDecoded = decodeUsername(username);
        UserModel user = userService.getUserModel(usernameDecoded);
        UserModel user = userService.getUserModel(usernameDecoded);
        return user;
    }
    /**
     * Returns the effective list of permissions for this user, taking into account
     * team memberships, ownerships.
     *
     *
     * @param user
     * @return the effective list of permissions for the user
     */
@@ -1140,7 +1140,7 @@
                set.add(rp);
            }
        }
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
        Collections.sort(list);
        return list;
@@ -1150,7 +1150,7 @@
     * Returns the list of users and their access permissions for the specified
     * repository including permission source information such as the team or
     * regular expression which sets the permission.
     *
     *
     * @param repository
     * @return a list of RegistrantAccessPermissions
     */
@@ -1173,10 +1173,10 @@
        }
        return list;
    }
    /**
     * Sets the access permissions to the specified repository for the specified users.
     *
     *
     * @param repository
     * @param permissions
     * @return true if the user models have been updated
@@ -1193,11 +1193,11 @@
        }
        return userService.updateUserModels(users);
    }
    /**
     * Returns the list of all users who have an explicit access permission
     * for the specified repository.
     *
     *
     * @see IUserService.getUsernamesForRepositoryRole(String)
     * @param repository
     * @return list of all usernames that have an access permission for the repository
@@ -1209,7 +1209,7 @@
    /**
     * Sets the list of all uses who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @see IUserService.setUsernamesForRepositoryRole(String, List<String>)
     * @param repository
     * @param usernames
@@ -1225,7 +1225,7 @@
    /**
     * Adds/updates a complete user object keyed by username. This method allows
     * for renaming a user.
     *
     *
     * @see IUserService.updateUserModel(String, UserModel)
     * @param username
     * @param user
@@ -1240,7 +1240,7 @@
                        "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
                        user.username));
            }
            // rename repositories and owner fields for all repositories
            for (RepositoryModel model : getRepositoryModels(user)) {
                if (model.isUsersPersonalRepository(username)) {
@@ -1265,7 +1265,7 @@
    /**
     * Returns the list of available teams that a user or repository may be
     * assigned to.
     *
     *
     * @return the list of teams
     */
    public List<String> getAllTeamnames() {
@@ -1276,7 +1276,7 @@
    /**
     * Returns the list of available teams that a user or repository may be
     * assigned to.
     *
     *
     * @return the list of teams
     */
    public List<TeamModel> getAllTeams() {
@@ -1286,19 +1286,19 @@
    /**
     * Returns the TeamModel object for the specified name.
     *
     *
     * @param teamname
     * @return a TeamModel object or null
     */
    public TeamModel getTeamModel(String teamname) {
        return userService.getTeamModel(teamname);
    }
    /**
     * Returns the list of teams and their access permissions for the specified
     * repository including the source of the permission such as the admin flag
     * or a regular expression.
     *
     *
     * @param repository
     * @return a list of RegistrantAccessPermissions
     */
@@ -1313,10 +1313,10 @@
        Collections.sort(list);
        return list;
    }
    /**
     * Sets the access permissions to the specified repository for the specified teams.
     *
     *
     * @param repository
     * @param permissions
     * @return true if the team models have been updated
@@ -1333,11 +1333,11 @@
        }
        return userService.updateTeamModels(teams);
    }
    /**
     * Returns the list of all teams who have an explicit access permission for
     * the specified repository.
     *
     *
     * @see IUserService.getTeamnamesForRepositoryRole(String)
     * @param repository
     * @return list of all teamnames with explicit access permissions to the repository
@@ -1349,7 +1349,7 @@
    /**
     * Sets the list of all uses who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @see IUserService.setTeamnamesForRepositoryRole(String, List<String>)
     * @param repository
     * @param teamnames
@@ -1364,7 +1364,7 @@
    /**
     * Updates the TeamModel object for the specified name.
     *
     *
     * @param teamname
     * @param team
     * @param isCreate
@@ -1385,7 +1385,7 @@
    /**
     * Delete the team object with the specified teamname
     *
     *
     * @see IUserService.deleteTeam(String)
     * @param teamname
     * @return true if successful
@@ -1393,17 +1393,17 @@
    public boolean deleteTeam(String teamname) {
        return userService.deleteTeam(teamname);
    }
    /**
     * Adds the repository to the list of cached repositories if Gitblit is
     * configured to cache the repository list.
     *
     *
     * @param model
     */
    private void addToCachedRepositoryList(RepositoryModel model) {
        if (settings.getBoolean(Keys.git.cacheRepositoryList, true)) {
            repositoryListCache.put(model.name.toLowerCase(), model);
            // update the fork origin repository with this repository clone
            if (!StringUtils.isEmpty(model.originRepository)) {
                if (repositoryListCache.containsKey(model.originRepository)) {
@@ -1413,10 +1413,10 @@
            }
        }
    }
    /**
     * Removes the repository from the list of cached repositories.
     *
     *
     * @param name
     * @return the model being removed
     */
@@ -1429,23 +1429,23 @@
    /**
     * Clears all the cached metadata for the specified repository.
     *
     *
     * @param repositoryName
     */
    private void clearRepositoryMetadataCache(String repositoryName) {
        repositorySizeCache.remove(repositoryName);
        repositoryMetricsCache.remove(repositoryName);
    }
    /**
     * Resets the repository list cache.
     *
     *
     */
    public void resetRepositoryListCache() {
        logger.info("Repository cache manually reset");
        repositoryListCache.clear();
    }
    /**
     * Calculate the checksum of settings that affect the repository list cache.
     * @return a checksum
@@ -1460,11 +1460,11 @@
        String checksum = StringUtils.getSHA1(ns.toString());
        return checksum;
    }
    /**
     * Compare the last repository list setting checksum to the current checksum.
     * If different then clear the cache so that it may be rebuilt.
     *
     *
     * @return true if the cached repository list is valid since the last check
     */
    private boolean isValidRepositoryList() {
@@ -1481,14 +1481,14 @@
    /**
     * Returns the list of all repositories available to Gitblit. This method
     * does not consider user access permissions.
     *
     *
     * @return list of all repositories
     */
    public List<String> getRepositoryList() {
        if (repositoryListCache.size() == 0 || !isValidRepositoryList()) {
            // we are not caching OR we have not yet cached OR the cached list is invalid
            long startTime = System.currentTimeMillis();
            List<String> repositories = JGitUtils.getRepositoryList(repositoriesFolder,
            List<String> repositories = JGitUtils.getRepositoryList(repositoriesFolder,
                    settings.getBoolean(Keys.git.onlyAccessBareRepositories, false),
                    settings.getBoolean(Keys.git.searchRepositoriesSubfolders, true),
                    settings.getInteger(Keys.git.searchRecursionDepth, -1),
@@ -1505,11 +1505,11 @@
                    // optionally (re)calculate repository sizes
                    msg = "{0} repositories identified with calculated folder sizes in {1} msecs";
                }
                for (String repository : repositories) {
                    getRepositoryModel(repository);
                }
                // rebuild fork networks
                for (RepositoryModel model : repositoryListCache.values()) {
                    if (!StringUtils.isEmpty(model.originRepository)) {
@@ -1519,12 +1519,12 @@
                        }
                    }
                }
                long duration = System.currentTimeMillis() - startTime;
                logger.info(MessageFormat.format(msg, repositoryListCache.size(), duration));
            }
        }
        // return sorted copy of cached list
        List<String> list = new ArrayList<String>();
        for (RepositoryModel model : repositoryListCache.values()) {
@@ -1536,7 +1536,7 @@
    /**
     * Returns the JGit repository for the specified name.
     *
     *
     * @param repositoryName
     * @return repository or null
     */
@@ -1546,7 +1546,7 @@
    /**
     * Returns the JGit repository for the specified name.
     *
     *
     * @param repositoryName
     * @param logError
     * @return repository or null
@@ -1555,7 +1555,7 @@
        // Decode url-encoded repository name (issue-278)
        // http://stackoverflow.com/questions/17183110
        repositoryName = repositoryName.replace("%7E", "~").replace("%7e", "~");
        if (isCollectingGarbage(repositoryName)) {
            logger.warn(MessageFormat.format("Rejecting request for {0}, busy collecting garbage!", repositoryName));
            return null;
@@ -1564,7 +1564,7 @@
        File dir = FileKey.resolve(new File(repositoriesFolder, repositoryName), FS.DETECTED);
        if (dir == null)
            return null;
        Repository r = null;
        try {
            FileKey key = FileKey.exact(dir, FS.DETECTED);
@@ -1580,7 +1580,7 @@
    /**
     * Returns the list of repository models that are accessible to the user.
     *
     *
     * @param user
     * @return list of repository models accessible to user
     */
@@ -1611,7 +1611,7 @@
    /**
     * Returns a repository model if the repository exists and the user may
     * access the repository.
     *
     *
     * @param user
     * @param repositoryName
     * @return repository model or null
@@ -1633,7 +1633,7 @@
    /**
     * Returns the repository model for the specified repository. This method
     * does not consider user access permissions.
     *
     *
     * @param repositoryName
     * @return repository model or null
     */
@@ -1650,7 +1650,7 @@
            addToCachedRepositoryList(model);
            return DeepCopier.copy(model);
        }
        // cached model
        RepositoryModel model = repositoryListCache.get(repositoryName.toLowerCase());
@@ -1669,7 +1669,7 @@
            logger.error(MessageFormat.format("Repository \"{0}\" is missing! Removing from cache.", repositoryName));
            return null;
        }
        FileBasedConfig config = (FileBasedConfig) getRepositoryConfig(r);
        if (config.isOutdated()) {
            // reload model
@@ -1678,7 +1678,7 @@
            removeFromCachedRepositoryList(model.name);
            addToCachedRepositoryList(model);
        } else {
            // update a few repository parameters
            // update a few repository parameters
            if (!model.hasCommits) {
                // update hasCommits, assume a repository only gains commits :)
                model.hasCommits = JGitUtils.hasCommits(r);
@@ -1687,14 +1687,14 @@
            updateLastChangeFields(r, model);
        }
        r.close();
        // return a copy of the cached model
        return DeepCopier.copy(model);
    }
    /**
     * Returns the star count of the repository.
     *
     *
     * @param repository
     * @return the star count
     */
@@ -1707,7 +1707,7 @@
        }
        return count;
    }
    private void reloadProjectMarkdown(ProjectModel project) {
        // project markdown
        File pmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/project.mkd");
@@ -1719,7 +1719,7 @@
            }
            project.projectMarkdown = projectMarkdownCache.getObject(project.name);
        }
        // project repositories markdown
        File rmkd = new File(getRepositoriesFolder(), (project.isRoot ? "" : project.name) + "/repositories.mkd");
        if (rmkd.exists()) {
@@ -1731,17 +1731,17 @@
            project.repositoriesMarkdown = projectRepositoriesMarkdownCache.getObject(project.name);
        }
    }
    /**
     * Returns the map of project config.  This map is cached and reloaded if
     * the underlying projects.conf file changes.
     *
     *
     * @return project config map
     */
    private Map<String, ProjectModel> getProjectConfigs() {
        if (projectCache.isEmpty() || projectConfigs.isOutdated()) {
            try {
                projectConfigs.load();
            } catch (Exception e) {
@@ -1765,9 +1765,9 @@
                }
                project.title = projectConfigs.getString("project", name, "title");
                project.description = projectConfigs.getString("project", name, "description");
                reloadProjectMarkdown(project);
                configs.put(name.toLowerCase(), project);
            }
            projectCache.clear();
@@ -1775,10 +1775,10 @@
        }
        return projectCache;
    }
    /**
     * Returns a list of project models for the user.
     *
     *
     * @param user
     * @param includeUsers
     * @return list of projects that are accessible to the user
@@ -1790,9 +1790,9 @@
        Map<String, ProjectModel> map = new TreeMap<String, ProjectModel>();
        // root project
        map.put("", configs.get(""));
        for (RepositoryModel model : getRepositoryModels(user)) {
            String rootPath = StringUtils.getRootPath(model.name).toLowerCase();
            String rootPath = StringUtils.getRootPath(model.name).toLowerCase();
            if (!map.containsKey(rootPath)) {
                ProjectModel project;
                if (configs.containsKey(rootPath)) {
@@ -1806,7 +1806,7 @@
            }
            map.get(rootPath).addRepository(model);
        }
        // sort projects, root project first
        List<ProjectModel> projects;
        if (includeUsers) {
@@ -1829,10 +1829,10 @@
        }
        return projects;
    }
    /**
     * Returns the project model for the specified user.
     *
     *
     * @param name
     * @param user
     * @return a project model, or null if it does not exist
@@ -1845,10 +1845,10 @@
        }
        return null;
    }
    /**
     * Returns a project model for the Gitblit/system user.
     *
     *
     * @param name a project name
     * @return a project model or null if the project does not exist
     */
@@ -1888,16 +1888,16 @@
            // no repositories == no project
            return null;
        }
        reloadProjectMarkdown(project);
        return project;
    }
    /**
     * Returns the list of project models that are referenced by the supplied
     * repository model    list.  This is an alternative method exists to ensure
     * Gitblit does not call getRepositoryModels(UserModel) twice in a request.
     *
     *
     * @param repositoryModels
     * @param includeUsers
     * @return a list of project models
@@ -1935,7 +1935,7 @@
        }
        return new ArrayList<ProjectModel>(projects.values());
    }
    /**
     * Workaround JGit.  I need to access the raw config object directly in order
     * to see if the config is dirty so that I can reload a repository model.
@@ -1943,7 +1943,7 @@
     * config.  If the config changes are made within Gitblit this is fine as
     * the returned config will still be flagged as dirty.  BUT... if the config
     * is manipulated outside Gitblit then it fails to recognize this as dirty.
     *
     *
     * @param r
     * @return a config
     */
@@ -1958,10 +1958,10 @@
        }
        return r.getConfig();
    }
    /**
     * Create a repository model from the configuration and repository data.
     *
     *
     * @param repositoryName
     * @return a repositoryModel or null if the repository does not exist
     */
@@ -1984,10 +1984,10 @@
            model.name = repositoryName;
        }
        model.projectPath = StringUtils.getFirstPathElement(repositoryName);
        StoredConfig config = r.getConfig();
        boolean hasOrigin = !StringUtils.isEmpty(config.getString("remote", "origin", "url"));
        if (config != null) {
            // Initialize description from description file
            if (getConfig(config,"description", null) == null) {
@@ -2046,7 +2046,7 @@
                    Constants.CONFIG_GITBLIT, null, "indexBranch")));
            model.metricAuthorExclusions = new ArrayList<String>(Arrays.asList(config.getStringList(
                    Constants.CONFIG_GITBLIT, null, "metricAuthorExclusions")));
            // Custom defined properties
            model.customFields = new LinkedHashMap<String, String>();
            for (String aProperty : config.getNames(Constants.CONFIG_GITBLIT, Constants.CONFIG_CUSTOM_FIELDS)) {
@@ -2059,7 +2059,7 @@
        model.hasCommits = JGitUtils.hasCommits(r);
        updateLastChangeFields(r, model);
        r.close();
        if (StringUtils.isEmpty(model.originRepository) && model.origin != null && model.origin.startsWith("file://")) {
            // repository was cloned locally... perhaps as a fork
            try {
@@ -2070,7 +2070,7 @@
                    File repoFolder = new File(getRepositoriesFolder(), originRepo);
                    if (repoFolder.exists()) {
                        model.originRepository = originRepo.toLowerCase();
                        // persist the fork origin
                        updateConfiguration(r, model);
                    }
@@ -2081,20 +2081,20 @@
        }
        return model;
    }
    /**
     * Determines if this server has the requested repository.
     *
     *
     * @param n
     * @return true if the repository exists
     */
    public boolean hasRepository(String repositoryName) {
        return hasRepository(repositoryName, false);
    }
    /**
     * Determines if this server has the requested repository.
     *
     *
     * @param n
     * @param caseInsensitive
     * @return true if the repository exists
@@ -2104,7 +2104,7 @@
            // if we are caching use the cache to determine availability
            // otherwise we end up adding a phantom repository to the cache
            return repositoryListCache.containsKey(repositoryName.toLowerCase());
        }
        }
        Repository r = getRepository(repositoryName, false);
        if (r == null) {
            return false;
@@ -2112,11 +2112,11 @@
        r.close();
        return true;
    }
    /**
     * Determines if the specified user has a fork of the specified origin
     * repository.
     *
     *
     * @param username
     * @param origin
     * @return true the if the user has a fork
@@ -2124,11 +2124,11 @@
    public boolean hasFork(String username, String origin) {
        return getFork(username, origin) != null;
    }
    /**
     * Gets the name of a user's fork of the specified origin
     * repository.
     *
     *
     * @param username
     * @param origin
     * @return the name of the user's fork, null otherwise
@@ -2150,7 +2150,7 @@
                        }
                    }
                }
                if (originModel.originRepository != null) {
                    roots.add(originModel.originRepository);
                    originModel = repositoryListCache.get(originModel.originRepository);
@@ -2159,7 +2159,7 @@
                    originModel = null;
                }
            }
            for (String repository : repositoryListCache.keySet()) {
                if (repository.startsWith(userPath)) {
                    RepositoryModel model = repositoryListCache.get(repository);
@@ -2190,11 +2190,11 @@
        // user does not have a fork
        return null;
    }
    /**
     * Returns the fork network for a repository by traversing up the fork graph
     * to discover the root and then down through all children of the root node.
     *
     *
     * @param repository
     * @return a ForkModel
     */
@@ -2217,7 +2217,7 @@
            return root;
        }
    }
    private ForkModel getForkModelFromCache(String repository) {
        RepositoryModel model = repositoryListCache.get(repository.toLowerCase());
        if (model == null) {
@@ -2234,7 +2234,7 @@
        }
        return fork;
    }
    private ForkModel getForkModel(String repository) {
        RepositoryModel model = getRepositoryModel(repository.toLowerCase());
        if (model == null) {
@@ -2257,7 +2257,7 @@
     * repository.  Gitblit caches the repository sizes to reduce the performance
     * penalty of recursive calculation. The cache is updated if the repository
     * has been changed since the last calculation.
     *
     *
     * @param model
     * @return size in bytes of the repository
     */
@@ -2284,7 +2284,7 @@
    /**
     * Ensure that a cached repository is completely closed and its resources
     * are properly released.
     *
     *
     * @param repositoryName
     */
    private void closeRepository(String repositoryName) {
@@ -2319,7 +2319,7 @@
                repository.close();
            }
        }
        // close any open index writer/searcher in the Lucene executor
        luceneExecutor.close(repositoryName);
    }
@@ -2329,7 +2329,7 @@
     * This method builds a metrics cache. The cache is updated if the
     * repository is updated. A new copy of the metrics list is returned on each
     * call so that modifications to the list are non-destructive.
     *
     *
     * @param model
     * @param repository
     * @return a new array list of metrics
@@ -2346,7 +2346,7 @@
    /**
     * Returns the gitblit string value for the specified key. If key is not
     * set, returns defaultValue.
     *
     *
     * @param config
     * @param field
     * @param defaultValue
@@ -2363,7 +2363,7 @@
    /**
     * Returns the gitblit boolean value for the specified key. If key is not
     * set, returns defaultValue.
     *
     *
     * @param config
     * @param field
     * @param defaultValue
@@ -2372,11 +2372,11 @@
    private boolean getConfig(StoredConfig config, String field, boolean defaultValue) {
        return config.getBoolean(Constants.CONFIG_GITBLIT, field, defaultValue);
    }
    /**
     * Returns the gitblit string value for the specified key. If key is not
     * set, returns defaultValue.
     *
     *
     * @param config
     * @param field
     * @param defaultValue
@@ -2398,11 +2398,11 @@
     * Creates/updates the repository model keyed by reopsitoryName. Saves all
     * repository settings in .git/config. This method allows for renaming
     * repositories and will update user access permissions accordingly.
     *
     *
     * All repositories created by this method are bare and automatically have
     * .git appended to their names, which is the standard convention for bare
     * repositories.
     *
     *
     * @param repositoryName
     * @param repository
     * @param isCreate
@@ -2473,7 +2473,7 @@
                            "Failed to rename repository permissions ''{0}'' to ''{1}''.",
                            repositoryName, repository.name));
                }
                // rename fork origins in their configs
                if (!ArrayUtils.isEmpty(repository.forks)) {
                    for (String fork : repository.forks) {
@@ -2491,7 +2491,7 @@
                        rf.close();
                    }
                }
                // update this repository's origin's fork list
                if (!StringUtils.isEmpty(repository.originRepository)) {
                    RepositoryModel origin = repositoryListCache.get(repository.originRepository);
@@ -2526,7 +2526,7 @@
            // only update symbolic head if it changes
            String currentRef = JGitUtils.getHEADRef(r);
            if (!StringUtils.isEmpty(repository.HEAD) && !repository.HEAD.equals(currentRef)) {
                logger.info(MessageFormat.format("Relinking {0} HEAD from {1} to {2}",
                logger.info(MessageFormat.format("Relinking {0} HEAD from {1} to {2}",
                        repository.name, currentRef, repository.HEAD));
                if (JGitUtils.setHEADtoRef(r, repository.HEAD)) {
                    // clear the cache
@@ -2549,10 +2549,10 @@
        // model will actually be replaced on next load because config is stale
        addToCachedRepositoryList(repository);
    }
    /**
     * Updates the Gitblit configuration for the specified repository.
     *
     *
     * @param r
     *            the Git repository
     * @param repository
@@ -2610,14 +2610,14 @@
            config.setString(Constants.CONFIG_GITBLIT, null, "commitMessageRenderer",
                    repository.commitMessageRenderer.name());
        }
        updateList(config, "federationSets", repository.federationSets);
        updateList(config, "preReceiveScript", repository.preReceiveScripts);
        updateList(config, "postReceiveScript", repository.postReceiveScripts);
        updateList(config, "mailingList", repository.mailingLists);
        updateList(config, "indexBranch", repository.indexedBranches);
        updateList(config, "metricAuthorExclusions", repository.metricAuthorExclusions);
        // User Defined Properties
        if (repository.customFields != null) {
            if (repository.customFields.size() == 0) {
@@ -2639,7 +2639,7 @@
            logger.error("Failed to save repository config!", e);
        }
    }
    private void updateList(StoredConfig config, String field, List<String> list) {
        // a null list is skipped, not cleared
        // this is for RPC administration where an older manager might be used
@@ -2656,7 +2656,7 @@
    /**
     * Deletes the repository from the file system and removes the repository
     * permission from all repository users.
     *
     *
     * @param model
     * @return true if successful
     */
@@ -2667,7 +2667,7 @@
    /**
     * Deletes the repository from the file system and removes the repository
     * permission from all repository users.
     *
     *
     * @param repositoryName
     * @return true if successful
     */
@@ -2676,7 +2676,7 @@
            closeRepository(repositoryName);
            // clear the repository cache
            clearRepositoryMetadataCache(repositoryName);
            RepositoryModel model = removeFromCachedRepositoryList(repositoryName);
            if (model != null && !ArrayUtils.isEmpty(model.forks)) {
                resetRepositoryListCache();
@@ -2699,9 +2699,9 @@
    /**
     * Returns an html version of the commit message with any global or
     * repository-specific regular expression substitution applied.
     *
     *
     * This method uses the preferred renderer to transform the commit message.
     *
     *
     * @param repository
     * @param text
     * @return html version of the commit message
@@ -2720,16 +2720,16 @@
            // noop
            break;
        }
        return processPlainCommitMessage(repository.name, text);
    }
    /**
     * Returns an html version of the commit message with any global or
     * repository-specific regular expression substitution applied.
     *
     *
     * This method assumes the commit message is plain text.
     *
     *
     * @param repositoryName
     * @param text
     * @return html version of the commit message
@@ -2738,13 +2738,13 @@
        String html = StringUtils.escapeForHtml(text, false);
        html = processCommitMessageRegex(repositoryName, html);
        return StringUtils.breakLinesForHtml(html);
    }
    /**
     * Apply globally or per-repository specified regex substitutions to the
     * commit message.
     *
     *
     * @param repositoryName
     * @param text
     * @return the processed commit message
@@ -2785,7 +2785,7 @@
    /**
     * Returns Gitblit's scheduled executor service for scheduling tasks.
     *
     *
     * @return scheduledExecutor
     */
    public ScheduledExecutorService executor() {
@@ -2833,7 +2833,7 @@
    /**
     * Returns the list of federated gitblit instances that this instance will
     * try to pull.
     *
     *
     * @return list of registered gitblit instances
     */
    public List<FederationModel> getFederationRegistrations() {
@@ -2845,7 +2845,7 @@
    /**
     * Retrieve the specified federation registration.
     *
     *
     * @param name
     *            the name of the registration
     * @return a federation registration
@@ -2869,7 +2869,7 @@
    /**
     * Returns the list of federation sets.
     *
     *
     * @return list of federation sets
     */
    public List<FederationSet> getFederationSets(String gitblitUrl) {
@@ -2892,7 +2892,7 @@
    /**
     * Returns the list of possible federation tokens for this Gitblit instance.
     *
     *
     * @return list of federation tokens
     */
    public List<String> getFederationTokens() {
@@ -2910,7 +2910,7 @@
    /**
     * Returns the specified federation token for this Gitblit instance.
     *
     *
     * @param type
     * @return a federation token
     */
@@ -2920,7 +2920,7 @@
    /**
     * Returns the specified federation token for this Gitblit instance.
     *
     *
     * @param value
     * @return a federation token
     */
@@ -2932,7 +2932,7 @@
    /**
     * Compares the provided token with this Gitblit instance's tokens and
     * determines if the requested permission may be granted to the token.
     *
     *
     * @param req
     * @param token
     * @return true if the request can be executed
@@ -2958,7 +2958,7 @@
    /**
     * Acknowledge and cache the status of a remote Gitblit instance.
     *
     *
     * @param identification
     *            the identification of the pulling Gitblit instance
     * @param registration
@@ -2978,7 +2978,7 @@
    /**
     * Returns the list of registration results.
     *
     *
     * @return the list of registration results
     */
    public List<FederationModel> getFederationResultRegistrations() {
@@ -2988,7 +2988,7 @@
    /**
     * Submit a federation proposal. The proposal is cached locally and the
     * Gitblit administrator(s) are notified via email.
     *
     *
     * @param proposal
     *            the proposal
     * @param gitblitUrl
@@ -3020,7 +3020,7 @@
    /**
     * Returns the list of pending federation proposals
     *
     *
     * @return list of federation proposals
     */
    public List<FederationProposal> getPendingFederationProposals() {
@@ -3046,7 +3046,7 @@
    /**
     * Get repositories for the specified token.
     *
     *
     * @param gitblitUrl
     *            the base url of this gitblit instance
     * @param token
@@ -3105,7 +3105,7 @@
    /**
     * Creates a proposal from the token.
     *
     *
     * @param gitblitUrl
     *            the url of this Gitblit instance
     * @param token
@@ -3127,7 +3127,7 @@
    /**
     * Returns the proposal identified by the supplied token.
     *
     *
     * @param token
     * @return the specified proposal or null
     */
@@ -3143,7 +3143,7 @@
    /**
     * Deletes a pending federation proposal.
     *
     *
     * @param a
     *            proposal
     * @return true if the proposal was deleted
@@ -3157,7 +3157,7 @@
    /**
     * Returns the list of all Groovy push hook scripts. Script files must have
     * .groovy extension
     *
     *
     * @return list of available hook scripts
     */
    public List<String> getAllScripts() {
@@ -3181,7 +3181,7 @@
    /**
     * Returns the list of pre-receive scripts the repository inherited from the
     * global settings and team affiliations.
     *
     *
     * @param repository
     *            if null only the globally specified scripts are returned
     * @return a list of scripts
@@ -3213,7 +3213,7 @@
     * Returns the list of all available Groovy pre-receive push hook scripts
     * that are not already inherited by the repository. Script files must have
     * .groovy extension
     *
     *
     * @param repository
     *            optional parameter
     * @return list of available hook scripts
@@ -3234,7 +3234,7 @@
    /**
     * Returns the list of post-receive scripts the repository inherited from
     * the global settings and team affiliations.
     *
     *
     * @param repository
     *            if null only the globally specified scripts are returned
     * @return a list of scripts
@@ -3265,7 +3265,7 @@
     * Returns the list of unused Groovy post-receive push hook scripts that are
     * not already inherited by the repository. Script files must have .groovy
     * extension
     *
     *
     * @param repository
     *            optional parameter
     * @return list of available hook scripts
@@ -3282,24 +3282,24 @@
        }
        return scripts;
    }
    /**
     * Search the specified repositories using the Lucene query.
     *
     *
     * @param query
     * @param page
     * @param pageSize
     * @param repositories
     * @return
     */
    public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) {
    public List<SearchResult> search(String query, int page, int pageSize, List<String> repositories) {
        List<SearchResult> srs = luceneExecutor.search(query, page, pageSize, repositories);
        return srs;
    }
    /**
     * Notify the administrators by email.
     *
     *
     * @param subject
     * @param message
     */
@@ -3310,7 +3310,7 @@
    /**
     * Notify users by email of something.
     *
     *
     * @param subject
     * @param message
     * @param toAddresses
@@ -3321,7 +3321,7 @@
    /**
     * Notify users by email of something.
     *
     *
     * @param subject
     * @param message
     * @param toAddresses
@@ -3335,16 +3335,16 @@
            Message mail = mailExecutor.createMessage(toAddresses);
            if (mail != null) {
                mail.setSubject(subject);
                MimeBodyPart messagePart = new MimeBodyPart();
                MimeBodyPart messagePart = new MimeBodyPart();
                messagePart.setText(message, "utf-8");
                messagePart.setHeader("Content-Type", "text/plain; charset=\"utf-8\"");
                messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable");
                MimeMultipart multiPart = new MimeMultipart();
                multiPart.addBodyPart(messagePart);
                mail.setContent(multiPart);
                mailExecutor.queue(mail);
            }
        } catch (MessagingException e) {
@@ -3354,7 +3354,7 @@
    /**
     * Notify users by email of something.
     *
     *
     * @param subject
     * @param message
     * @param toAddresses
@@ -3365,7 +3365,7 @@
    /**
     * Notify users by email of something.
     *
     *
     * @param subject
     * @param message
     * @param toAddresses
@@ -3379,12 +3379,12 @@
            Message mail = mailExecutor.createMessage(toAddresses);
            if (mail != null) {
                mail.setSubject(subject);
                MimeBodyPart messagePart = new MimeBodyPart();
                MimeBodyPart messagePart = new MimeBodyPart();
                messagePart.setText(message, "utf-8");
                messagePart.setHeader("Content-Type", "text/html; charset=\"utf-8\"");
                messagePart.setHeader("Content-Transfer-Encoding", "quoted-printable");
                MimeMultipart multiPart = new MimeMultipart();
                multiPart.addBodyPart(messagePart);
                mail.setContent(multiPart);
@@ -3398,7 +3398,7 @@
    /**
     * Returns the descriptions/comments of the Gitblit config settings.
     *
     *
     * @return SettingsModel
     */
    public ServerSettings getSettingsModel() {
@@ -3411,7 +3411,7 @@
                setting.name = key;
                settingsModel.add(setting);
            }
            setting.currentValue = settings.getString(key, "");
            setting.currentValue = settings.getString(key, "");
        }
        settingsModel.pushScripts = getAllScripts();
        return settingsModel;
@@ -3421,7 +3421,7 @@
     * Parse the properties file and aggregate all the comments by the setting
     * key. A setting model tracks the current value, the default value, the
     * description of the setting and and directives about the setting.
     *
     *
     * @return Map<String, SettingModel>
     */
    private ServerSettings loadSettingModels() {
@@ -3490,7 +3490,7 @@
     * Configure the Gitblit singleton with the specified settings source. This
     * source may be file settings (Gitblit GO) or may be web.xml settings
     * (Gitblit WAR).
     *
     *
     * @param settings
     */
    public void configureContext(IStoredSettings settings, File folder, boolean startFederation) {
@@ -3507,7 +3507,7 @@
        mailExecutor = new MailExecutor(settings);
        luceneExecutor = new LuceneExecutor(settings, repositoriesFolder);
        gcExecutor = new GCExecutor(settings);
        // initialize utilities
        String prefix = settings.getString(Keys.git.userRepositoryPrefix, "~");
        ModelUtils.setUserRepoPrefix(prefix);
@@ -3520,7 +3520,7 @@
            logger.info("Identifying available repositories...");
            getRepositoryList();
        }
        logTimezone("JVM", TimeZone.getDefault());
        logTimezone(Constants.NAME, getTimezone());
@@ -3538,12 +3538,12 @@
            }
            setUserService(loginService);
        }
        // load and cache the project metadata
        projectConfigs = new FileBasedConfig(getFileOrFolder(Keys.web.projectsFile, "${baseFolder}/projects.conf"), FS.detect());
        getProjectConfigs();
        configureMailExecutor();
        configureMailExecutor();
        configureLuceneIndexing();
        configureGarbageCollector();
        if (startFederation) {
@@ -3556,7 +3556,7 @@
        ContainerUtils.CVE_2007_0450.test();
    }
    protected void configureMailExecutor() {
        if (mailExecutor.isReady()) {
            logger.info("Mail executor is scheduled to process the message queue every 2 minutes.");
@@ -3565,12 +3565,12 @@
            logger.warn("Mail server is not properly configured.  Mail services disabled.");
        }
    }
    protected void configureLuceneIndexing() {
        scheduledExecutor.scheduleAtFixedRate(luceneExecutor, 1, 2,  TimeUnit.MINUTES);
        logger.info("Lucene executor is scheduled to process indexed branches every 2 minutes.");
    }
    protected void configureGarbageCollector() {
        // schedule gc engine
        if (gcExecutor.isReady()) {
@@ -3590,13 +3590,13 @@
            delay = (int) ((cd.getTime() - now.getTime())/TimeUtils.MIN);
            String when = delay + " mins";
            if (delay > 60) {
                when = MessageFormat.format("{0,number,0.0} hours", ((float)delay)/60f);
                when = MessageFormat.format("{0,number,0.0} hours", (delay)/60f);
            }
            logger.info(MessageFormat.format("Next scheculed GC scan is in {0}", when));
            scheduledExecutor.scheduleAtFixedRate(gcExecutor, delay, 60*24, TimeUnit.MINUTES);
        }
    }
    protected void configureJGit() {
        // Configure JGit
        WindowCacheConfig cfg = new WindowCacheConfig();
@@ -3620,7 +3620,7 @@
            logger.error("Failed to configure JGit parameters!", e);
        }
    }
    protected void configureFanout() {
        // startup Fanout PubSub service
        if (settings.getInteger(Keys.fanout.port, 0) > 0) {
@@ -3648,7 +3648,7 @@
            fanoutService.start();
        }
    }
    protected void configureGitDaemon() {
        int port = settings.getInteger(Keys.git.daemonPort, 0);
        String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
@@ -3662,7 +3662,7 @@
            }
        }
    }
    protected void configureCommitCache() {
        int daysToCache = settings.getInteger(Keys.web.activityCacheDays, 14);
        if (daysToCache <= 0) {
@@ -3698,11 +3698,11 @@
                    daysToCache, commitCount, repoCount, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start)));
        }
    }
    protected final Logger getLogger() {
        return logger;
    }
    protected final ScheduledExecutorService getScheduledExecutor() {
        return scheduledExecutor;
    }
@@ -3710,7 +3710,7 @@
    protected final LuceneExecutor getLuceneExecutor() {
        return luceneExecutor;
    }
    private void logTimezone(String type, TimeZone zone) {
        SimpleDateFormat df = new SimpleDateFormat("z Z");
        df.setTimeZone(zone);
@@ -3721,7 +3721,7 @@
    /**
     * Configure Gitblit from the web.xml, if no configuration has already been
     * specified.
     *
     *
     * @see ServletContextListener.contextInitialize(ServletContextEvent)
     */
    @Override
@@ -3734,7 +3734,7 @@
            String contextRealPath = context.getRealPath("/");
            File contextFolder = (contextRealPath != null) ? new File(contextRealPath) : null;
            String openShift = System.getenv("OPENSHIFT_DATA_DIR");
            if (!StringUtils.isEmpty(openShift)) {
                // Gitblit is running in OpenShift/JBoss
                File base = new File(openShift);
@@ -3743,7 +3743,7 @@
                // gitblit.properties setting overrides
                File overrideFile = new File(base, "gitblit.properties");
                webxmlSettings.applyOverrides(overrideFile);
                // Copy the included scripts to the configured groovy folder
                String path = webxmlSettings.getString(Keys.groovy.scriptsFolder, "groovy");
                File localScripts = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, base, path);
@@ -3759,15 +3759,15 @@
                        }
                    }
                }
                // configure context using the web.xml
                configureContext(webxmlSettings, base, true);
            } else {
                // Gitblit is running in a standard servlet container
                logger.info("WAR contextFolder is " + ((contextFolder != null) ? contextFolder.getAbsolutePath() : "<empty>"));
                String path = webxmlSettings.getString(Constants.baseFolder, Constants.contextFolder$ + "/WEB-INF/data");
                if (path.contains(Constants.contextFolder$) && contextFolder == null) {
                    // warn about null contextFolder (issue-199)
                    logger.error("");
@@ -3777,7 +3777,7 @@
                    logger.error(MessageFormat.format("OR configure your servlet container to specify a \"{0}\" parameter in the context configuration!!", Constants.baseFolder));
                    logger.error("");
                }
                File base = com.gitblit.utils.FileUtils.resolveParameter(Constants.contextFolder$, contextFolder, path);
                base.mkdirs();
@@ -3788,15 +3788,15 @@
                }
                // delegate all config to baseFolder/gitblit.properties file
                FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
                FileSettings settings = new FileSettings(localSettings.getAbsolutePath());
                configureContext(settings, base, true);
            }
        }
        settingsModel = loadSettingModels();
        serverStatus.servletContainer = servletContext.getServerInfo();
    }
    protected void extractResources(ServletContext context, String path, File toDir) {
        for (String resource : context.getResourcePaths(path)) {
            // extract the resource to the directory if it does not exist
@@ -3861,18 +3861,18 @@
            gitDaemon.stop();
        }
    }
    /**
     *
     *
     * @return true if we are running the gc executor
     */
    public boolean isCollectingGarbage() {
        return gcExecutor.isRunning();
    }
    /**
     * Returns true if Gitblit is actively collecting garbage in this repository.
     *
     *
     * @param repositoryName
     * @return true if actively collecting garbage
     */
@@ -3883,8 +3883,8 @@
    /**
     * Creates a personal fork of the specified repository. The clone is view
     * restricted by default and the owner of the source repository is given
     * access to the clone.
     *
     * access to the clone.
     *
     * @param repository
     * @param user
     * @return the repository model of the fork, if successful
@@ -3944,7 +3944,7 @@
            }
            cloneTeams.add(cloneTeam);
        }
        userService.updateTeamModels(cloneTeams);
        userService.updateTeamModels(cloneTeams);
        // add this clone to the cached model
        addToCachedRepositoryList(cloneModel);
@@ -3954,7 +3954,7 @@
    /**
     * Allow to understand if GitBlit supports and is configured to allow
     * cookie-based authentication.
     *
     *
     * @return status of Cookie authentication enablement.
     */
    public boolean allowCookieAuthentication() {
src/main/java/com/gitblit/GitBlitException.java
@@ -19,9 +19,9 @@
/**
 * GitBlitException is a marginally useful class. :)
 *
 *
 * @author James Moger
 *
 *
 */
public class GitBlitException extends IOException {
src/main/java/com/gitblit/GitBlitServer.java
@@ -75,9 +75,9 @@
 * simplify command line parameter processing. This class also automatically
 * generates a self-signed certificate for localhost, if the keystore does not
 * already exist.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitBlitServer {
@@ -85,7 +85,7 @@
    public static void main(String... args) {
        GitBlitServer server = new GitBlitServer();
        // filter out the baseFolder parameter
        List<String> filtered = new ArrayList<String>();
        String folder = "data";
@@ -103,7 +103,7 @@
                filtered.add(arg);
            }
        }
        Params.baseFolder = folder;
        Params params = new Params();
        JCommander jc = new JCommander(params);
@@ -125,7 +125,7 @@
    /**
     * Display the command line usage of Gitblit GO.
     *
     *
     * @param jc
     * @param t
     */
@@ -172,7 +172,7 @@
        FileSettings settings = params.FILESETTINGS;
        if (!StringUtils.isEmpty(params.settingsfile)) {
            if (new File(params.settingsfile).exists()) {
                settings = new FileSettings(params.settingsfile);
                settings = new FileSettings(params.settingsfile);
            }
        }
        logger = LoggerFactory.getLogger(GitBlitServer.class);
@@ -198,7 +198,7 @@
        String osname = System.getProperty("os.name");
        String osversion = System.getProperty("os.version");
        logger.info("Running on " + osname + " (" + osversion + ")");
        List<Connector> connectors = new ArrayList<Connector>();
        // conditionally configure the http connector
@@ -236,7 +236,7 @@
                NewCertificateConfig certificateConfig = NewCertificateConfig.KEY.parse(config);
                certificateConfig.update(metadata);
            }
            metadata.notAfter = new Date(System.currentTimeMillis() + 10*TimeUtils.ONEYEAR);
            X509Utils.prepareX509Infrastructure(metadata, baseFolder, new X509Log() {
                @Override
@@ -260,7 +260,7 @@
                }
            });
            if (serverKeyStore.exists()) {
            if (serverKeyStore.exists()) {
                Connector secureConnector = createSSLConnector(params.alias, serverKeyStore, serverTrustStore, params.storePassword,
                        caRevocationList, params.useNIO, params.securePort, settings.getInteger(Keys.server.threadPoolSize, 50), params.requireClientCertificates);
                String bindInterface = settings.getString(Keys.server.httpsBindInterface, null);
@@ -297,7 +297,7 @@
        // tempDir is where the embedded Gitblit web application is expanded and
        // where Jetty creates any necessary temporary files
        File tempDir = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.temp);
        File tempDir = com.gitblit.utils.FileUtils.resolveParameter(Constants.baseFolder$, baseFolder, params.temp);
        if (tempDir.exists()) {
            try {
                FileUtils.delete(tempDir, FileUtils.RECURSIVE | FileUtils.RETRY);
@@ -343,7 +343,7 @@
        settings.overrideSetting(Keys.realm.userService, params.userService);
        settings.overrideSetting(Keys.git.repositoriesFolder, params.repositoriesFolder);
        settings.overrideSetting(Keys.git.daemonPort, params.gitPort);
        // Start up an in-memory LDAP server, if configured
        try {
            if (StringUtils.isEmpty(params.ldapLdifFile) == false) {
@@ -354,21 +354,21 @@
                    String rootDN = firstLine.substring(4);
                    String bindUserName = settings.getString(Keys.realm.ldap.username, "");
                    String bindPassword = settings.getString(Keys.realm.ldap.password, "");
                    // Get the port
                    int port = ldapUrl.getPort();
                    if (port == -1)
                        port = 389;
                    InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(rootDN);
                    config.addAdditionalBindCredentials(bindUserName, bindPassword);
                    config.setListenerConfigs(InMemoryListenerConfig.createLDAPConfig("default", port));
                    config.setSchema(null);
                    InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
                    ds.importFromLDIF(true, new LDIFReader(ldifFile));
                    ds.startListening();
                    logger.info("LDAP Server started at ldap://localhost:" + port);
                }
            }
@@ -400,14 +400,14 @@
            System.exit(100);
        }
    }
    protected GitBlit getGitBlitInstance() {
        return GitBlit.self();
    }
    /**
     * Creates an http connector.
     *
     *
     * @param useNIO
     * @param port
     * @param threadPoolSize
@@ -439,10 +439,10 @@
    /**
     * Creates an https connector.
     *
     *
     * SSL renegotiation will be enabled if the JVM is 1.6.0_22 or later.
     * oracle.com/technetwork/java/javase/documentation/tlsreadme2-176330.html
     *
     *
     * @param certAlias
     * @param keyStore
     * @param clientTrustStore
@@ -455,7 +455,7 @@
     * @return an https connector
     */
    private Connector createSSLConnector(String certAlias, File keyStore, File clientTrustStore,
            String storePassword, File caRevocationList, boolean useNIO,  int port, int threadPoolSize,
            String storePassword, File caRevocationList, boolean useNIO,  int port, int threadPoolSize,
            boolean requireClientCertificates) {
        GitblitSslContextFactory factory = new GitblitSslContextFactory(certAlias,
                keyStore, clientTrustStore, storePassword, caRevocationList);
@@ -486,10 +486,10 @@
        return connector;
    }
    /**
     * Creates an ajp connector.
     *
     *
     * @param port
     * @return an ajp connector
     */
@@ -505,7 +505,7 @@
    /**
     * Tests to see if the operating system is Windows.
     *
     *
     * @return true if this is a windows machine
     */
    private boolean isWindows() {
@@ -516,9 +516,9 @@
     * The ShutdownMonitorThread opens a socket on a specified port and waits
     * for an incoming connection. When that connection is accepted a shutdown
     * message is issued to the running Jetty server.
     *
     *
     * @author James Moger
     *
     *
     */
    private static class ShutdownMonitorThread extends Thread {
@@ -634,7 +634,7 @@
         */
        @Parameter(names = { "--settings" }, description = "Path to alternative settings")
        public String settingsfile;
        @Parameter(names = { "--ldapLdifFile" }, description = "Path to LDIF file.  This will cause an in-memory LDAP server to be started according to gitblit settings")
        public String ldapLdifFile;
src/main/java/com/gitblit/GitFilter.java
@@ -27,9 +27,9 @@
 * The GitFilter is an AccessRestrictionFilter which ensures that Git client
 * requests for push, clone, or view restricted repositories are authenticated
 * and authorized.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitFilter extends AccessRestrictionFilter {
@@ -42,7 +42,7 @@
    /**
     * Extract the repository name from the url.
     *
     *
     * @param cloneUrl
     * @return repository name
     */
@@ -59,7 +59,7 @@
    /**
     * Extract the repository name from the url.
     *
     *
     * @param url
     * @return repository name
     */
@@ -71,7 +71,7 @@
    /**
     * Analyze the url and returns the action of the request. Return values are
     * either "/git-receive-pack" or "/git-upload-pack".
     *
     *
     * @param serverUrl
     * @return action of the request
     */
@@ -92,20 +92,20 @@
        }
        return null;
    }
    /**
     * Determine if a non-existing repository can be created using this filter.
     *
     *
     * @return true if the server allows repository creation on-push
     */
    @Override
    protected boolean isCreationAllowed() {
        return GitBlit.getBoolean(Keys.git.allowCreateOnPush, true);
    }
    /**
     * Determine if the repository can receive pushes.
     *
     *
     * @param repository
     * @param action
     * @return true if the action may be performed
@@ -124,7 +124,7 @@
    /**
     * Determine if the repository requires authentication.
     *
     *
     * @param repository
     * @param action
     * @return true if authentication required
@@ -133,7 +133,7 @@
    protected boolean requiresAuthentication(RepositoryModel repository, String action) {
        if (gitUploadPack.equals(action)) {
            // send to client
            return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
            return repository.accessRestriction.atLeast(AccessRestrictionType.CLONE);
        } else if (gitReceivePack.equals(action)) {
            // receive from client
            return repository.accessRestriction.atLeast(AccessRestrictionType.PUSH);
@@ -144,7 +144,7 @@
    /**
     * Determine if the user can access the repository and perform the specified
     * action.
     *
     *
     * @param repository
     * @param user
     * @param action
@@ -155,7 +155,7 @@
        if (!GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
            // Git Servlet disabled
            return false;
        }
        }
        if (action.equals(gitReceivePack)) {
            // Push request
            if (user.canPush(repository)) {
@@ -179,11 +179,11 @@
        }
        return true;
    }
    /**
     * An authenticated user with the CREATE role can create a repository on
     * push.
     *
     *
     * @param user
     * @param repository
     * @param action
@@ -203,7 +203,7 @@
                if (repository.contains("/../")) {
                    logger.error(MessageFormat.format("Illegal relative path in repository name! {0}", repository));
                    return null;
                }
                }
                // confirm valid characters in repository name
                Character c = StringUtils.findInvalidCharacter(repository);
@@ -239,7 +239,7 @@
                logger.warn(MessageFormat.format("{0} is not permitted to create repository {1} ON-PUSH!", user.username, repository));
            }
        }
        // repository could not be created or action was not a push
        return null;
    }
src/main/java/com/gitblit/GitblitSslContextFactory.java
@@ -32,7 +32,7 @@
/**
 * Special SSL context factory that configures Gitblit GO and replaces the
 * primary trustmanager with a GitblitTrustManager.
 *
 *
 * @author James Moger
 */
public class GitblitSslContextFactory extends SslContextFactory {
@@ -40,11 +40,11 @@
    private static final Logger logger = LoggerFactory.getLogger(GitblitSslContextFactory.class);
    private final File caRevocationList;
    public GitblitSslContextFactory(String certAlias, File keyStore, File clientTrustStore,
            String storePassword, File caRevocationList) {
        super(keyStore.getAbsolutePath());
        this.caRevocationList = caRevocationList;
        // disable renegotiation unless this is a patched JVM
@@ -65,8 +65,8 @@
            logger.info("   allowing SSL renegotiation on Java " + v);
            setAllowRenegotiate(allowRenegotiation);
        }
        if (!StringUtils.isEmpty(certAlias)) {
            logger.info("   certificate alias = " + certAlias);
            setCertAlias(certAlias);
@@ -74,7 +74,7 @@
        setKeyStorePassword(storePassword);
        setTrustStore(clientTrustStore.getAbsolutePath());
        setTrustStorePassword(storePassword);
        logger.info("   keyStorePath   = " + keyStore.getAbsolutePath());
        logger.info("   trustStorePath = " + clientTrustStore.getAbsolutePath());
        logger.info("   crlPath        = " + caRevocationList.getAbsolutePath());
src/main/java/com/gitblit/GitblitTrustManager.java
@@ -32,20 +32,20 @@
import org.slf4j.LoggerFactory;
/**
 * GitblitTrustManager is a wrapper trust manager that hot-reloads a local file
 * GitblitTrustManager is a wrapper trust manager that hot-reloads a local file
 * CRL and enforces client certificate revocations.  The GitblitTrustManager
 * also implements fuzzy revocation enforcement in case of issuer mismatch BUT
 * serial number match.  These rejecions are specially noted in the log.
 *
 *
 * @author James Moger
 */
public class GitblitTrustManager implements X509TrustManager {
    private static final Logger logger = LoggerFactory.getLogger(GitblitTrustManager.class);
    private final X509TrustManager delegate;
    private final File caRevocationList;
    private final AtomicLong lastModified = new AtomicLong(0);
    private volatile X509CRL crl;
@@ -77,7 +77,7 @@
    public X509Certificate[] getAcceptedIssuers() {
        return delegate.getAcceptedIssuers();
    }
    protected boolean isRevoked(X509Certificate cert) {
        if (!caRevocationList.exists()) {
            return false;
@@ -88,7 +88,7 @@
            // exact cert is revoked
            return true;
        }
        X509CRLEntry entry = crl.getRevokedCertificate(cert.getSerialNumber());
        if (entry != null) {
            logger.warn("Certificate issuer does not match CRL issuer, but serial number has been revoked!");
@@ -96,10 +96,10 @@
            logger.warn("   crl issuer  = " + crl.getIssuerX500Principal());
            return true;
        }
        return false;
    }
    protected synchronized void read() {
        if (lastModified.get() == caRevocationList.lastModified()) {
            return;
src/main/java/com/gitblit/HtpasswdUserService.java
@@ -40,7 +40,7 @@
/**
 * Implementation of a user service using an Apache htpasswd file for authentication.
 *
 *
 * This user service implement custom authentication using entries in a file created
 * by the 'htpasswd' program of an Apache web server. All possible output
 * options of the 'htpasswd' program version 2.2 are supported:
@@ -48,7 +48,7 @@
 * glibc crypt() (not on Windows and NetWare),
 * Apache MD5 (apr1),
 * unsalted SHA-1.
 *
 *
 * Configuration options:
 * realm.htpasswd.backingUserService - Specify the backing user service that is used
 *                                     to keep the user data other than the password.
@@ -59,7 +59,7 @@
 * realm.htpasswd.overrideLocalAuthentication - Specify if local accounts are overwritten
 *                                              when authentication matches for an
 *                                              external account.
 *
 *
 * @author Florian Zschocke
 *
 */
@@ -110,12 +110,12 @@
    /**
     * Setup the user service.
     *
     *
     * The HtpasswdUserService extends the GitblitUserService and is thus
     * backed by the available user services provided by the GitblitUserService.
     * In addition the setup tries to read and parse the htpasswd file to be used
     * for authentication.
     *
     *
     * @param settings
     * @since 0.7.0
     */
@@ -238,9 +238,9 @@
    /**
     * Determine if the account is to be treated as a local account.
     *
     *
     * This influences authentication. A local account will be authenticated
     * by the backing user service while an external account will be handled
     * by the backing user service while an external account will be handled
     * by this user service.
     * <br/>
     * The decision also depends on the setting of the key
@@ -254,7 +254,8 @@
     * If the key is set to false, then it is determined if the account is local
     * according to the logic of the GitblitUserService.
     */
    protected boolean isLocalAccount(String username)
    @Override
    protected boolean isLocalAccount(String username)
    {
        if ( settings.getBoolean(KEY_OVERRIDE_LOCALAUTH, DEFAULT_OVERRIDE_LOCALAUTH) ) {
            read();
@@ -270,7 +271,8 @@
     *
     * @return AccountType.HTPASSWD
     */
    protected AccountType getAccountType()
    @Override
    protected AccountType getAccountType()
    {
        return AccountType.HTPASSWD;
    }
src/main/java/com/gitblit/IStoredSettings.java
@@ -28,9 +28,9 @@
/**
 * Base class for stored settings implementations.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class IStoredSettings {
@@ -53,7 +53,7 @@
    /**
     * Returns the list of keys whose name starts with the specified prefix. If
     * the prefix is null or empty, all key names are returned.
     *
     *
     * @param startingWith
     * @return list of keys
     */
@@ -78,7 +78,7 @@
     * Returns the boolean value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as a boolean, the
     * defaultValue is returned.
     *
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
@@ -98,7 +98,7 @@
     * Returns the integer value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an integer, the
     * defaultValue is returned.
     *
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
@@ -123,7 +123,7 @@
     * Returns the long value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an long, the
     * defaultValue is returned.
     *
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
@@ -143,7 +143,7 @@
        }
        return defaultValue;
    }
    /**
     * Returns an int filesize from a string value such as 50m or 50mb
     * @param name
@@ -158,7 +158,7 @@
        }
        return com.gitblit.utils.FileUtils.convertSizeToInt(val, defaultValue);
    }
    /**
     * Returns an long filesize from a string value such as 50m or 50mb
     * @param n
@@ -178,7 +178,7 @@
     * Returns the char value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a char, the
     * defaultValue is returned.
     *
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
@@ -198,7 +198,7 @@
     * Returns the string value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a string, the
     * defaultValue is returned.
     *
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
@@ -213,11 +213,11 @@
        }
        return defaultValue;
    }
    /**
     * Returns the string value for the specified key.  If the key does not
     * exist an exception is thrown.
     *
     *
     * @param key
     * @return key value
     */
@@ -228,13 +228,13 @@
            if (value != null) {
                return value.trim();
            }
        }
        }
        throw new RuntimeException("Property (" + name + ") does not exist");
    }
    /**
     * Returns a list of space-separated strings from the specified key.
     *
     *
     * @param name
     * @return list of strings
     */
@@ -245,7 +245,7 @@
    /**
     * Returns a list of strings from the specified key using the specified
     * string separator.
     *
     *
     * @param name
     * @param separator
     * @return list of strings
@@ -259,10 +259,10 @@
        }
        return strings;
    }
    /**
     * Returns a list of space-separated integers from the specified key.
     *
     *
     * @param name
     * @return list of strings
     */
@@ -273,7 +273,7 @@
    /**
     * Returns a list of integers from the specified key using the specified
     * string separator.
     *
     *
     * @param name
     * @param separator
     * @return list of integers
@@ -294,10 +294,10 @@
        }
        return ints;
    }
    /**
     * Returns a map of strings from the specified key.
     *
     *
     * @param name
     * @return map of string, string
     */
@@ -306,7 +306,7 @@
        for (String string : getStrings(name)) {
            String[] kvp = string.split("=", 2);
            String key = kvp[0];
            String value = kvp[1];
            String value = kvp[1];
            map.put(key,  value);
        }
        return map;
@@ -314,7 +314,7 @@
    /**
     * Override the specified key with the specified value.
     *
     *
     * @param key
     * @param value
     */
@@ -324,7 +324,7 @@
    /**
     * Override the specified key with the specified value.
     *
     *
     * @param key
     * @param value
     */
@@ -335,7 +335,7 @@
    /**
     * Updates the values for the specified keys and persists the entire
     * configuration file.
     *
     *
     * @param map
     *            of key, value pairs
     * @return true if successful
src/main/java/com/gitblit/IUserService.java
@@ -24,9 +24,9 @@
/**
 * Implementations of IUserService control all aspects of UserModel objects and
 * user authentication.
 *
 *
 * @author James Moger
 *
 *
 */
public interface IUserService {
@@ -34,7 +34,7 @@
     * Setup the user service. This method allows custom implementations to
     * retrieve settings from gitblit.properties or the web.xml file without
     * relying on the GitBlit static singleton.
     *
     *
     * @param settings
     * @since 0.7.0
     */
@@ -42,46 +42,46 @@
    /**
     * Does the user service support changes to credentials?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
     */
    boolean supportsCredentialChanges();
    /**
     * Does the user service support changes to user display name?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
     */
    boolean supportsDisplayNameChanges();
    /**
     * Does the user service support changes to user email address?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
     */
    boolean supportsEmailAddressChanges();
    /**
     * Does the user service support changes to team memberships?
     *
     *
     * @return true or false
     * @since 1.0.0
     */
     */
    boolean supportsTeamMembershipChanges();
    /**
     * Does the user service support cookie authentication?
     *
     *
     * @return true or false
     */
    boolean supportsCookies();
    /**
     * Returns the cookie value for the specified user.
     *
     *
     * @param model
     * @return cookie value
     */
@@ -89,7 +89,7 @@
    /**
     * Authenticate a user based on their cookie.
     *
     *
     * @param cookie
     * @return a user object or null
     */
@@ -97,7 +97,7 @@
    /**
     * Authenticate a user based on a username and password.
     *
     *
     * @param username
     * @param password
     * @return a user object or null
@@ -106,14 +106,14 @@
    /**
     * Logout a user.
     *
     *
     * @param user
     */
    void logout(UserModel user);
    /**
     * Retrieve the user object for the specified username.
     *
     *
     * @param username
     * @return a user object or null
     */
@@ -121,7 +121,7 @@
    /**
     * Updates/writes a complete user object.
     *
     *
     * @param model
     * @return true if update is successful
     */
@@ -129,17 +129,17 @@
    /**
     * Updates/writes all specified user objects.
     *
     *
     * @param models a list of user models
     * @return true if update is successful
     * @since 1.2.0
     */
    boolean updateUserModels(Collection<UserModel> models);
    /**
     * Adds/updates a user object keyed by username. This method allows for
     * renaming a user.
     *
     *
     * @param username
     *            the old username
     * @param model
@@ -150,7 +150,7 @@
    /**
     * Deletes the user object from the user service.
     *
     *
     * @param model
     * @return true if successful
     */
@@ -158,7 +158,7 @@
    /**
     * Delete the user object with the specified username
     *
     *
     * @param username
     * @return true if successful
     */
@@ -166,14 +166,14 @@
    /**
     * Returns the list of all users available to the login service.
     *
     *
     * @return list of all usernames
     */
    List<String> getAllUsernames();
    /**
     * Returns the list of all users available to the login service.
     *
     *
     * @return list of all users
     * @since 0.8.0
     */
@@ -181,35 +181,35 @@
    /**
     * Returns the list of all teams available to the login service.
     *
     *
     * @return list of all teams
     * @since 0.8.0
     */
     */
    List<String> getAllTeamNames();
    /**
     * Returns the list of all teams available to the login service.
     *
     *
     * @return list of all teams
     * @since 0.8.0
     */
     */
    List<TeamModel> getAllTeams();
    /**
     * Returns the list of all users who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @return list of all usernames that can bypass the access restriction
     * @since 0.8.0
     */
     */
    List<String> getTeamnamesForRepositoryRole(String role);
    /**
     * Sets the list of all teams who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @param teamnames
@@ -218,38 +218,38 @@
     */
    @Deprecated
    boolean setTeamnamesForRepositoryRole(String role, List<String> teamnames);
    /**
     * Retrieve the team object for the specified team name.
     *
     *
     * @param teamname
     * @return a team object or null
     * @since 0.8.0
     */
     */
    TeamModel getTeamModel(String teamname);
    /**
     * Updates/writes a complete team object.
     *
     *
     * @param model
     * @return true if update is successful
     * @since 0.8.0
     */
     */
    boolean updateTeamModel(TeamModel model);
    /**
     * Updates/writes all specified team objects.
     *
     *
     * @param models a list of team models
     * @return true if update is successful
     * @since 1.2.0
     */
     */
    boolean updateTeamModels(Collection<TeamModel> models);
    /**
     * Updates/writes and replaces a complete team object keyed by teamname.
     * This method allows for renaming a team.
     *
     *
     * @param teamname
     *            the old teamname
     * @param model
@@ -261,7 +261,7 @@
    /**
     * Deletes the team object from the user service.
     *
     *
     * @param model
     * @return true if successful
     * @since 0.8.0
@@ -270,17 +270,17 @@
    /**
     * Delete the team object with the specified teamname
     *
     *
     * @param teamname
     * @return true if successful
     * @since 0.8.0
     */
     */
    boolean deleteTeam(String teamname);
    /**
     * Returns the list of all users who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @return list of all usernames that can bypass the access restriction
@@ -291,7 +291,7 @@
    /**
     * Sets the list of all uses who are allowed to bypass the access
     * restriction placed on the specified repository.
     *
     *
     * @param role
     *            the repository name
     * @param usernames
@@ -302,7 +302,7 @@
    /**
     * Renames a repository role.
     *
     *
     * @param oldRole
     * @param newRole
     * @return true if successful
@@ -311,7 +311,7 @@
    /**
     * Removes a repository role from all users.
     *
     *
     * @param role
     * @return true if successful
     */
@@ -321,5 +321,6 @@
     * @See java.lang.Object.toString();
     * @return string representation of the login service
     */
    @Override
    String toString();
}
src/main/java/com/gitblit/JsonServlet.java
@@ -33,20 +33,20 @@
/**
 * Servlet class for interpreting json requests.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class JsonServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    protected final int forbiddenCode = HttpServletResponse.SC_FORBIDDEN;
    protected final int notAllowedCode = HttpServletResponse.SC_METHOD_NOT_ALLOWED;
    protected final int failureCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
    protected final Logger logger;
    public JsonServlet() {
@@ -56,7 +56,7 @@
    /**
     * Processes an gson request.
     *
     *
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
src/main/java/com/gitblit/Launcher.java
@@ -33,9 +33,9 @@
 * folders and then calls the application main. Using this technique we do not
 * have to specify a classpath and we can dynamically add jars to the
 * distribution.
 *
 *
 * @author James Moger
 *
 *
 */
public class Launcher {
@@ -120,7 +120,7 @@
    /**
     * Adds a file to the classpath
     *
     *
     * @param f
     *            the file to be added
     * @throws IOException
src/main/java/com/gitblit/LdapUserService.java
@@ -49,7 +49,7 @@
/**
 * Implementation of an LDAP user service.
 *
 *
 * @author John Crygier
 */
public class LdapUserService extends GitblitUserService {
@@ -58,7 +58,7 @@
    private IStoredSettings settings;
    private AtomicLong lastLdapUserSync = new AtomicLong(0L);
    public LdapUserService() {
        super();
    }
@@ -74,19 +74,19 @@
            throw new IllegalArgumentException(Keys.realm.ldap.ldapCachePeriod + " must have format '<long> <TimeUnit>' where <TimeUnit> is one of 'MILLISECONDS', 'SECONDS', 'MINUTES', 'HOURS', 'DAYS'");
        }
    }
    @Override
    public void setup(IStoredSettings settings) {
        this.settings = settings;
        String file = settings.getString(Keys.realm.ldap.backingUserService, "${baseFolder}/users.conf");
        File realmFile = GitBlit.getFileOrFolder(file);
        serviceImpl = createUserService(realmFile);
        logger.info("LDAP User Service backed by " + serviceImpl.toString());
        synchronizeLdapUsers();
    }
    protected synchronized void synchronizeLdapUsers() {
        final boolean enabled = settings.getBoolean(Keys.realm.ldap.synchronizeUsers.enable, false);
        if (enabled) {
@@ -150,7 +150,7 @@
                                updateTeamModels(userTeams.values());
                            }
                        }
                        lastLdapUserSync.set(System.currentTimeMillis());
                        lastLdapUserSync.set(System.currentTimeMillis());
                    } finally {
                        ldapConnection.close();
                    }
@@ -158,18 +158,18 @@
            }
        }
    }
    private LDAPConnection getLdapConnection() {
        try {
            URI ldapUrl = new URI(settings.getRequiredString(Keys.realm.ldap.server));
            String bindUserName = settings.getString(Keys.realm.ldap.username, "");
            String bindPassword = settings.getString(Keys.realm.ldap.password, "");
            int ldapPort = ldapUrl.getPort();
            if (ldapUrl.getScheme().equalsIgnoreCase("ldaps")) {    // SSL
                if (ldapPort == -1)    // Default Port
                    ldapPort = 636;
                LDAPConnection conn;
                SSLUtil sslUtil = new SSLUtil(new TrustAllTrustManager());
                if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
@@ -184,9 +184,9 @@
                LDAPConnection conn;
                if (StringUtils.isEmpty(bindUserName) && StringUtils.isEmpty(bindPassword)) {
                    conn = new LDAPConnection(ldapUrl.getHost(), ldapPort);
                    conn = new LDAPConnection(ldapUrl.getHost(), ldapPort);
                } else {
                    conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
                    conn = new LDAPConnection(ldapUrl.getHost(), ldapPort, bindUserName, bindPassword);
                }
                if (ldapUrl.getScheme().equalsIgnoreCase("ldap+tls")) {
@@ -208,10 +208,10 @@
        } catch (LDAPException e) {
            logger.error("Error Connecting to LDAP", e);
        }
        return null;
    }
    /**
     * Credentials are defined in the LDAP server and can not be manipulated
     * from Gitblit.
@@ -223,7 +223,7 @@
    public boolean supportsCredentialChanges() {
        return false;
    }
    /**
     * If no displayName pattern is defined then Gitblit can manage the display name.
     *
@@ -234,7 +234,7 @@
    public boolean supportsDisplayNameChanges() {
        return StringUtils.isEmpty(settings.getString(Keys.realm.ldap.displayName, ""));
    }
    /**
     * If no email pattern is defined then Gitblit can manage the email address.
     *
@@ -246,19 +246,20 @@
        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
     * changes must be made on the LDAP server by the LDAP administrator.
     *
     *
     * @return true or false
     * @since 1.0.0
     */
     */
    @Override
    public boolean supportsTeamMembershipChanges() {
        return !settings.getBoolean(Keys.realm.ldap.maintainTeams, false);
    }
    @Override
    protected AccountType getAccountType() {
         return AccountType.LDAP;
@@ -270,9 +271,9 @@
            // local account, bypass LDAP authentication
            return super.authenticate(username, password);
        }
        String simpleUsername = getSimpleUsername(username);
        LDAPConnection ldapConnection = getLdapConnection();
        if (ldapConnection != null) {
            try {
@@ -313,7 +314,7 @@
                                    updateTeamModel(userTeam);
                            }
                        }
                        return user;
                    }
                }
@@ -321,14 +322,14 @@
                ldapConnection.close();
            }
        }
        return null;
        return null;
    }
    /**
     * Set the admin attribute from team memberships retrieved from LDAP.
     * If we are not storing teams in LDAP and/or we have not defined any
     * administrator teams, then do not change the admin flag.
     *
     *
     * @param user
     */
    private void setAdminAttribute(UserModel user) {
@@ -349,17 +350,17 @@
            }
        }
    }
    private void setUserAttributes(UserModel user, SearchResultEntry userEntry) {
        // Is this user an admin?
        setAdminAttribute(user);
        // Don't want visibility into the real password, make up a dummy
        user.password = Constants.EXTERNAL_ACCOUNT;
        user.accountType = getAccountType();
        // Get full name Attribute
        String displayName = settings.getString(Keys.realm.ldap.displayName, "");
        String displayName = settings.getString(Keys.realm.ldap.displayName, "");
        if (!StringUtils.isEmpty(displayName)) {
            // Replace embedded ${} with attributes
            if (displayName.contains("${")) {
@@ -374,7 +375,7 @@
                }
            }
        }
        // Get email address Attribute
        String email = settings.getString(Keys.realm.ldap.email, "");
        if (!StringUtils.isEmpty(email)) {
@@ -394,39 +395,39 @@
    private void getTeamsFromLdap(LDAPConnection ldapConnection, String simpleUsername, SearchResultEntry loggingInUser, UserModel user) {
        String loggingInUserDN = loggingInUser.getDN();
        user.teams.clear();        // Clear the users team memberships - we're going to get them from LDAP
        String groupBase = settings.getString(Keys.realm.ldap.groupBase, "");
        String groupMemberPattern = settings.getString(Keys.realm.ldap.groupMemberPattern, "(&(objectClass=group)(member=${dn}))");
        groupMemberPattern = StringUtils.replace(groupMemberPattern, "${dn}", escapeLDAPSearchFilter(loggingInUserDN));
        groupMemberPattern = StringUtils.replace(groupMemberPattern, "${username}", escapeLDAPSearchFilter(simpleUsername));
        // Fill in attributes into groupMemberPattern
        for (Attribute userAttribute : loggingInUser.getAttributes())
            groupMemberPattern = StringUtils.replace(groupMemberPattern, "${" + userAttribute.getName() + "}", escapeLDAPSearchFilter(userAttribute.getValue()));
        SearchResult teamMembershipResult = doSearch(ldapConnection, groupBase, groupMemberPattern);
        if (teamMembershipResult != null && teamMembershipResult.getEntryCount() > 0) {
            for (int i = 0; i < teamMembershipResult.getEntryCount(); i++) {
                SearchResultEntry teamEntry = teamMembershipResult.getSearchEntries().get(i);
                String teamName = teamEntry.getAttribute("cn").getValue();
                TeamModel teamModel = getTeamModel(teamName);
                if (teamModel == null)
                    teamModel = createTeamFromLdap(teamEntry);
                user.teams.add(teamModel);
                teamModel.addUser(user.getName());
            }
        }
    }
    private TeamModel createTeamFromLdap(SearchResultEntry teamEntry) {
        TeamModel answer = new TeamModel(teamEntry.getAttributeValue("cn"));
        // potentially retrieve other attributes here in the future
        return answer;
        return answer;
    }
    private SearchResult doSearch(LDAPConnection ldapConnection, String base, String filter) {
@@ -434,11 +435,11 @@
            return ldapConnection.search(base, SearchScope.SUB, filter);
        } catch (LDAPSearchException e) {
            logger.error("Problem Searching LDAP", e);
            return null;
        }
    }
    private boolean isAuthenticated(LDAPConnection ldapConnection, String userDn, String password) {
        try {
            // Binding will stop any LDAP-Injection Attacks since the searched-for user needs to bind to that DN
@@ -461,10 +462,10 @@
        synchronizeLdapUsers();
        return super.getAllUsers();
    }
    /**
     * Returns a simple username without any domain prefixes.
     *
     *
     * @param username
     * @return a simple username
     */
@@ -473,10 +474,10 @@
        if (lastSlash > -1) {
            username = username.substring(lastSlash + 1);
        }
        return username;
    }
    // From: https://www.owasp.org/index.php/Preventing_LDAP_Injection_in_Java
    public static final String escapeLDAPSearchFilter(String filter) {
        StringBuilder sb = new StringBuilder();
@@ -495,8 +496,8 @@
            case ')':
                sb.append("\\29");
                break;
            case '\u0000':
                sb.append("\\00");
            case '\u0000':
                sb.append("\\00");
                break;
            default:
                sb.append(curChar);
src/main/java/com/gitblit/LogoServlet.java
@@ -29,20 +29,20 @@
/**
 * Handles requests for logo.png
 *
 *
 * @author James Moger
 *
 *
 */
public class LogoServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
    private static final long lastModified = System.currentTimeMillis();
    public LogoServlet() {
        super();
    }
    @Override
    protected long getLastModified(HttpServletRequest req) {
        File file = GitBlit.getFileOrFolder(Keys.web.headerLogo, "${baseFolder}/logo.png");
@@ -52,7 +52,7 @@
            return lastModified;
        }
    }
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
@@ -71,7 +71,7 @@
                // default logo
                response.setDateHeader("Last-Modified", lastModified);
                is = getClass().getResourceAsStream("/logo.png");
            }
            }
            if (contentType == null) {
                contentType = "image/png";
            }
src/main/java/com/gitblit/LuceneExecutor.java
@@ -95,13 +95,13 @@
/**
 * The Lucene executor handles indexing and searching repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class LuceneExecutor implements Runnable {
    private static final int INDEX_VERSION = 5;
    private static final String FIELD_OBJECT_TYPE = "type";
@@ -121,20 +121,20 @@
    private static final String CONF_VERSION = "version";
    private static final String CONF_ALIAS = "aliases";
    private static final String CONF_BRANCH = "branches";
    private static final Version LUCENE_VERSION = Version.LUCENE_35;
    private final Logger logger = LoggerFactory.getLogger(LuceneExecutor.class);
    private final IStoredSettings storedSettings;
    private final File repositoriesFolder;
    private final Map<String, IndexSearcher> searchers = new ConcurrentHashMap<String, IndexSearcher>();
    private final Map<String, IndexWriter> writers = new ConcurrentHashMap<String, IndexWriter>();
    private final String luceneIgnoreExtensions = "7z arc arj bin bmp dll doc docx exe gif gz jar jpg lib lzh odg odf odt pdf ppt png so swf xcf xls xlsx zip";
    private Set<String> excludedExtensions;
    public LuceneExecutor(IStoredSettings settings, File repositoriesFolder) {
        this.storedSettings = settings;
        this.repositoriesFolder = repositoriesFolder;
@@ -146,7 +146,7 @@
    }
    /**
     * Run is executed by the Gitblit executor service.  Because this is called
     * Run is executed by the Gitblit executor service.  Because this is called
     * by an executor service, calls will queue - i.e. there can never be
     * concurrent execution of repository index updates.
     */
@@ -164,7 +164,7 @@
            // busy collecting garbage, try again later
            return;
        }
        for (String repositoryName: GitBlit.self().getRepositoryList()) {
            RepositoryModel model = GitBlit.self().getRepositoryModel(repositoryName);
            if (model.hasCommits && !ArrayUtils.isEmpty(model.indexedBranches)) {
@@ -175,7 +175,7 @@
                    }
                    continue;
                }
                index(model, repository);
                index(model, repository);
                repository.close();
                System.gc();
            }
@@ -185,7 +185,7 @@
    /**
     * Synchronously indexes a repository. This may build a complete index of a
     * repository or it may update an existing index.
     *
     *
     * @param name
     *            the name of the repository
     * @param repository
@@ -225,10 +225,10 @@
            logger.error(MessageFormat.format("Lucene indexing failure for {0}", model.name), t);
        }
    }
    /**
     * Close the writer/searcher objects for a repository.
     *
     *
     * @param repositoryName
     */
    public synchronized void close(String repositoryName) {
@@ -240,7 +240,7 @@
        } catch (Exception e) {
            logger.error("Failed to close index searcher for " + repositoryName, e);
        }
        try {
            IndexWriter writer = writers.remove(repositoryName);
            if (writer != null) {
@@ -248,12 +248,12 @@
            }
        } catch (Exception e) {
            logger.error("Failed to close index writer for " + repositoryName, e);
        }
        }
    }
    /**
     * Close all Lucene indexers.
     *
     *
     */
    public synchronized void close() {
        // close all writers
@@ -277,10 +277,10 @@
        searchers.clear();
    }
    /**
     * Deletes the Lucene index for the specified repository.
     *
     *
     * @param repositoryName
     * @return true, if successful
     */
@@ -306,10 +306,10 @@
            throw new RuntimeException(e);
        }
    }
    /**
     * Returns the author for the commit, if this information is available.
     *
     *
     * @param commit
     * @return an author or unknown
     */
@@ -320,14 +320,14 @@
            if (StringUtils.isEmpty(name)) {
                name = commit.getAuthorIdent().getEmailAddress();
            }
        } catch (NullPointerException n) {
        } catch (NullPointerException n) {
        }
        return name;
    }
    /**
     * Returns the committer for the commit, if this information is available.
     *
     *
     * @param commit
     * @return an committer or unknown
     */
@@ -338,11 +338,11 @@
            if (StringUtils.isEmpty(name)) {
                name = commit.getCommitterIdent().getEmailAddress();
            }
        } catch (NullPointerException n) {
        } catch (NullPointerException n) {
        }
        return name;
    }
    /**
     * Get the tree associated with the given commit.
     *
@@ -363,7 +363,7 @@
    /**
     * Construct a keyname from the branch.
     *
     *
     * @param branchName
     * @return a keyname appropriate for the Git config file format
     */
@@ -373,7 +373,7 @@
    /**
     * Returns the Lucene configuration for the specified repository.
     *
     *
     * @param repository
     * @return a config object
     */
@@ -387,7 +387,7 @@
     * Reads the Lucene config file for the repository to check the index
     * version. If the index version is different, then rebuild the repository
     * index.
     *
     *
     * @param repository
     * @return true of the on-disk index format is different than INDEX_VERSION
     */
@@ -407,13 +407,13 @@
    /**
     * This completely indexes the repository and will destroy any existing
     * index.
     *
     *
     * @param repositoryName
     * @param repository
     * @return IndexResult
     */
    public IndexResult reindex(RepositoryModel model, Repository repository) {
        IndexResult result = new IndexResult();
        IndexResult result = new IndexResult();
        if (!deleteIndex(model.name)) {
            return result;
        }
@@ -434,12 +434,12 @@
                }
                tags.get(tag.getReferencedObjectId().getName()).add(tag.displayName);
            }
            ObjectReader reader = repository.newObjectReader();
            // get the local branches
            List<RefModel> branches = JGitUtils.getLocalBranches(repository, true, -1);
            // sort them by most recently updated
            Collections.sort(branches, new Comparator<RefModel>() {
                @Override
@@ -447,7 +447,7 @@
                    return ref2.getDate().compareTo(ref1.getDate());
                }
            });
            // reorder default branch to first position
            RefModel defaultBranch = null;
            ObjectId defaultBranchId = JGitUtils.getDefaultBranch(repository);
@@ -459,7 +459,7 @@
            }
            branches.remove(defaultBranch);
            branches.add(0, defaultBranch);
            // walk through each branch
            for (RefModel branch : branches) {
@@ -475,7 +475,7 @@
                    // normal explicit branch check
                    indexBranch = model.indexedBranches.contains(branch.getName());
                }
                // if this branch is not specifically indexed then skip
                if (!indexBranch) {
                    continue;
@@ -493,22 +493,22 @@
                // index the blob contents of the tree
                TreeWalk treeWalk = new TreeWalk(repository);
                treeWalk.addTree(tip.getTree());
                treeWalk.setRecursive(true);
                treeWalk.setRecursive(true);
                Map<String, ObjectId> paths = new TreeMap<String, ObjectId>();
                while (treeWalk.next()) {
                    // ensure path is not in a submodule
                    if (treeWalk.getFileMode(0) != FileMode.GITLINK) {
                        paths.put(treeWalk.getPathString(), treeWalk.getObjectId(0));
                    }
                }
                }
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                byte[] tmp = new byte[32767];
                RevWalk commitWalk = new RevWalk(reader);
                commitWalk.markStart(tip);
                RevCommit commit;
                while ((paths.size() > 0) && (commit = commitWalk.next()) != null) {
                    TreeWalk diffWalk = new TreeWalk(reader);
@@ -532,17 +532,17 @@
                        if (!paths.containsKey(path)) {
                            continue;
                        }
                        // remove path from set
                        ObjectId blobId = paths.remove(path);
                        result.blobCount++;
                        // index the blob metadata
                        String blobAuthor = getAuthor(commit);
                        String blobCommitter = getCommitter(commit);
                        String blobDate = DateTools.timeToString(commit.getCommitTime() * 1000L,
                                Resolution.MINUTE);
                        Document doc = new Document();
                        doc.add(new Field(FIELD_OBJECT_TYPE, SearchObjectType.blob.name(), Store.YES, Index.NOT_ANALYZED_NO_NORMS));
                        doc.add(new Field(FIELD_BRANCH, branchName, Store.YES, Index.ANALYZED));
@@ -550,7 +550,7 @@
                        doc.add(new Field(FIELD_PATH, path, Store.YES, Index.ANALYZED));
                        doc.add(new Field(FIELD_DATE, blobDate, Store.YES, Index.NO));
                        doc.add(new Field(FIELD_AUTHOR, blobAuthor, Store.YES, Index.ANALYZED));
                        doc.add(new Field(FIELD_COMMITTER, blobCommitter, Store.YES, Index.ANALYZED));
                        doc.add(new Field(FIELD_COMMITTER, blobCommitter, Store.YES, Index.ANALYZED));
                        // determine extension to compare to the extension
                        // blacklist
@@ -561,20 +561,20 @@
                        }
                        // index the blob content
                        if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) {
                        if (StringUtils.isEmpty(ext) || !excludedExtensions.contains(ext)) {
                            ObjectLoader ldr = repository.open(blobId, Constants.OBJ_BLOB);
                            InputStream in = ldr.openStream();
                            InputStream in = ldr.openStream();
                            int n;
                            while ((n = in.read(tmp)) > 0) {
                                os.write(tmp, 0, n);
                            }
                            in.close();
                            byte[] content = os.toByteArray();
                            String str = StringUtils.decodeString(content, encodings);
                            String str = StringUtils.decodeString(content, encodings);
                            doc.add(new Field(FIELD_CONTENT, str, Store.YES, Index.ANALYZED));
                            os.reset();
                        }
                        }
                        // add the blob to the index
                        writer.addDocument(doc);
                    }
@@ -608,7 +608,7 @@
            // finished
            reader.release();
            // commit all changes and reset the searcher
            config.setInt(CONF_INDEX, null, CONF_VERSION, INDEX_VERSION);
            config.save();
@@ -620,11 +620,11 @@
        }
        return result;
    }
    /**
     * Incrementally update the index with the specified commit for the
     * repository.
     *
     *
     * @param repositoryName
     * @param repository
     * @param branch
@@ -632,7 +632,7 @@
     * @param commit
     * @return true, if successful
     */
    private IndexResult index(String repositoryName, Repository repository,
    private IndexResult index(String repositoryName, Repository repository,
            String branch, RevCommit commit) {
        IndexResult result = new IndexResult();
        try {
@@ -681,7 +681,7 @@
                }
            }
            writer.commit();
            // get any annotated commit tags
            List<String> commitTags = new ArrayList<String>();
            for (RefModel ref : JGitUtils.getTags(repository, false, -1)) {
@@ -689,7 +689,7 @@
                    commitTags.add(ref.displayName);
                }
            }
            // create and write the Lucene document
            Document doc = createDocument(commit, commitTags);
            doc.add(new Field(FIELD_BRANCH, branch, Store.YES, Index.ANALYZED));
@@ -703,7 +703,7 @@
    /**
     * Delete a blob from the specified branch of the repository index.
     *
     *
     * @param repositoryName
     * @param branch
     * @param path
@@ -713,7 +713,7 @@
    public boolean deleteBlob(String repositoryName, String branch, String path) throws Exception {
        String pattern = MessageFormat.format("{0}:'{'0} AND {1}:\"'{'1'}'\" AND {2}:\"'{'2'}'\"", FIELD_OBJECT_TYPE, FIELD_BRANCH, FIELD_PATH);
        String q = MessageFormat.format(pattern, SearchObjectType.blob.name(), branch, path);
        BooleanQuery query = new BooleanQuery();
        StandardAnalyzer analyzer = new StandardAnalyzer(LUCENE_VERSION);
        QueryParser qp = new QueryParser(LUCENE_VERSION, FIELD_SUMMARY, analyzer);
@@ -721,7 +721,7 @@
        IndexWriter writer = getIndexWriter(repositoryName);
        int numDocsBefore = writer.numDocs();
        writer.deleteDocuments(query);
        writer.deleteDocuments(query);
        writer.commit();
        int numDocsAfter = writer.numDocs();
        if (numDocsBefore == numDocsAfter) {
@@ -735,7 +735,7 @@
    /**
     * Updates a repository index incrementally from the last indexed commits.
     *
     *
     * @param model
     * @param repository
     * @return IndexResult
@@ -770,7 +770,7 @@
            // get the local branches
            List<RefModel> branches = JGitUtils.getLocalBranches(repository, true, -1);
            // sort them by most recently updated
            Collections.sort(branches, new Comparator<RefModel>() {
                @Override
@@ -778,7 +778,7 @@
                    return ref2.getDate().compareTo(ref1.getDate());
                }
            });
            // reorder default branch to first position
            RefModel defaultBranch = null;
            ObjectId defaultBranchId = JGitUtils.getDefaultBranch(repository);
@@ -790,7 +790,7 @@
            }
            branches.remove(defaultBranch);
            branches.add(0, defaultBranch);
            // walk through each branches
            for (RefModel branch : branches) {
                String branchName = branch.getName();
@@ -807,15 +807,15 @@
                    // normal explicit branch check
                    indexBranch = model.indexedBranches.contains(branch.getName());
                }
                // if this branch is not specifically indexed then skip
                if (!indexBranch) {
                    continue;
                }
                // remove this branch from the deletedBranches set
                deletedBranches.remove(branchName);
                // determine last commit
                String keyName = getBranchKey(branchName);
                String lastCommit = config.getString(CONF_BRANCH, null, keyName);
@@ -832,10 +832,10 @@
                if (revs.size() > 0) {
                    result.branchCount += 1;
                }
                // reverse the list of commits so we start with the first commit
                // reverse the list of commits so we start with the first commit
                Collections.reverse(revs);
                for (RevCommit commit : revs) {
                for (RevCommit commit : revs) {
                    // index a commit
                    result.add(index(model.name, repository, branchName, commit));
                }
@@ -862,10 +862,10 @@
        }
        return result;
    }
    /**
     * Creates a Lucene document for a commit
     *
     *
     * @param commit
     * @param tags
     * @return a Lucene document
@@ -889,13 +889,13 @@
    /**
     * Incrementally index an object for the repository.
     *
     *
     * @param repositoryName
     * @param doc
     * @return true, if successful
     */
    private boolean index(String repositoryName, Document doc) {
        try {
        try {
            IndexWriter writer = getIndexWriter(repositoryName);
            writer.addDocument(doc);
            writer.commit();
@@ -913,7 +913,7 @@
        result.totalHits = totalHits;
        result.score = score;
        result.date = DateTools.stringToDate(doc.get(FIELD_DATE));
        result.summary = doc.get(FIELD_SUMMARY);
        result.summary = doc.get(FIELD_SUMMARY);
        result.author = doc.get(FIELD_AUTHOR);
        result.committer = doc.get(FIELD_COMMITTER);
        result.type = SearchObjectType.fromName(doc.get(FIELD_OBJECT_TYPE));
@@ -935,7 +935,7 @@
    /**
     * Gets an index searcher for the repository.
     *
     *
     * @param repository
     * @return
     * @throws IOException
@@ -953,16 +953,16 @@
    /**
     * Gets an index writer for the repository. The index will be created if it
     * does not already exist or if forceCreate is specified.
     *
     *
     * @param repository
     * @return an IndexWriter
     * @throws IOException
     */
    private IndexWriter getIndexWriter(String repository) throws IOException {
        IndexWriter indexWriter = writers.get(repository);
        IndexWriter indexWriter = writers.get(repository);
        File repositoryFolder = FileKey.resolve(new File(repositoriesFolder, repository), FS.DETECTED);
        File indexFolder = new File(repositoryFolder, LUCENE_DIR);
        Directory directory = FSDirectory.open(indexFolder);
        Directory directory = FSDirectory.open(indexFolder);
        if (indexWriter == null) {
            if (!indexFolder.exists()) {
@@ -979,7 +979,7 @@
    /**
     * Searches the specified repositories for the given text or query
     *
     *
     * @param text
     *            if the text is null or empty, null is returned
     * @param page
@@ -990,7 +990,7 @@
     *            a list of repositories to search. if no repositories are
     *            specified null is returned.
     * @return a list of SearchResults in order from highest to the lowest score
     *
     *
     */
    public List<SearchResult> search(String text, int page, int pageSize, List<String> repositories) {
        if (ArrayUtils.isEmpty(repositories)) {
@@ -998,10 +998,10 @@
        }
        return search(text, page, pageSize, repositories.toArray(new String[0]));
    }
    /**
     * Searches the specified repositories for the given text or query
     *
     *
     * @param text
     *            if the text is null or empty, null is returned
     * @param page
@@ -1012,7 +1012,7 @@
     *            a list of repositories to search. if no repositories are
     *            specified null is returned.
     * @return a list of SearchResults in order from highest to the lowest score
     *
     *
     */
    public List<SearchResult> search(String text, int page, int pageSize, String... repositories) {
        if (StringUtils.isEmpty(text)) {
@@ -1034,7 +1034,7 @@
            qp = new QueryParser(LUCENE_VERSION, FIELD_CONTENT, analyzer);
            qp.setAllowLeadingWildcard(true);
            query.add(qp.parse(text), Occur.SHOULD);
            IndexSearcher searcher;
            if (repositories.length == 1) {
                // single repository search
@@ -1050,7 +1050,7 @@
                MultiSourceReader reader = new MultiSourceReader(rdrs);
                searcher = new IndexSearcher(reader);
            }
            Query rewrittenQuery = searcher.rewrite(query);
            logger.debug(rewrittenQuery.toString());
@@ -1072,7 +1072,7 @@
                    int index = reader.getSourceIndex(docId);
                    result.repository = repositories[index];
                }
                String content = doc.get(FIELD_CONTENT);
                String content = doc.get(FIELD_CONTENT);
                result.fragment = getHighlightedFragment(analyzer, query, content, result);
                results.add(result);
            }
@@ -1081,9 +1081,9 @@
        }
        return new ArrayList<SearchResult>(results);
    }
    /**
     *
     *
     * @param analyzer
     * @param query
     * @param content
@@ -1096,18 +1096,18 @@
            String content, SearchResult result) throws IOException, InvalidTokenOffsetsException {
        if (content == null) {
            content = "";
        }
        }
        int fragmentLength = SearchObjectType.commit == result.type ? 512 : 150;
        QueryScorer scorer = new QueryScorer(query, "content");
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer, fragmentLength);
        Fragmenter fragmenter = new SimpleSpanFragmenter(scorer, fragmentLength);
        // use an artificial delimiter for the token
        String termTag = "!!--[";
        String termTagEnd = "]--!!";
        SimpleHTMLFormatter formatter = new SimpleHTMLFormatter(termTag, termTagEnd);
        Highlighter highlighter = new Highlighter(formatter, scorer);
        Highlighter highlighter = new Highlighter(formatter, scorer);
        highlighter.setTextFragmenter(fragmenter);
        String [] fragments = highlighter.getBestFragments(analyzer, "content", content, 3);
@@ -1122,14 +1122,14 @@
            }
            return "<pre class=\"text\">" + StringUtils.escapeForHtml(fragment, true) + "</pre>";
        }
        // make sure we have unique fragments
        Set<String> uniqueFragments = new LinkedHashSet<String>();
        for (String fragment : fragments) {
            uniqueFragments.add(fragment);
        }
        fragments = uniqueFragments.toArray(new String[uniqueFragments.size()]);
        StringBuilder sb = new StringBuilder();
        for (int i = 0, len = fragments.length; i < len; i++) {
            String fragment = fragments[i];
@@ -1140,7 +1140,7 @@
            // determine position of the raw fragment in the content
            int pos = content.indexOf(raw);
            // restore complete first line of fragment
            int c = pos;
            while (c > 0) {
@@ -1153,11 +1153,11 @@
                // inject leading chunk of first fragment line
                fragment = content.substring(c + 1, pos) + fragment;
            }
            if (SearchObjectType.blob  == result.type) {
                // count lines as offset into the content for this fragment
                int line = Math.max(1, StringUtils.countLines(content.substring(0, pos)));
                // create fragment tag with line number and language
                String lang = "";
                String ext = StringUtils.getFileExtension(result.path).toLowerCase();
@@ -1166,9 +1166,9 @@
                    lang = " lang-" + ext;
                }
                tag = MessageFormat.format("<pre class=\"prettyprint linenums:{0,number,0}{1}\">", line, lang);
            }
            sb.append(tag);
            // replace the artificial delimiter with html tags
@@ -1181,10 +1181,10 @@
            }
        }
        return sb.toString();
    }
    }
    /**
     * Simple class to track the results of an index update.
     * Simple class to track the results of an index update.
     */
    private class IndexResult {
        long startTime = System.currentTimeMillis();
@@ -1193,33 +1193,33 @@
        int branchCount;
        int commitCount;
        int blobCount;
        void add(IndexResult result) {
            this.branchCount += result.branchCount;
            this.commitCount += result.commitCount;
            this.blobCount += result.blobCount;
        }
        void success() {
            success = true;
            endTime = System.currentTimeMillis();
        }
        float duration() {
            return (endTime - startTime)/1000f;
        }
    }
    /**
     * Custom subclass of MultiReader to identify the source index for a given
     * doc id.  This would not be necessary of there was a public method to
     * obtain this information.
     *
     *
     */
    private class MultiSourceReader extends MultiReader {
        final Method method;
        MultiSourceReader(IndexReader[] subReaders) {
            super(subReaders);
            Method m = null;
@@ -1231,7 +1231,7 @@
            }
            method = m;
        }
        int getSourceIndex(int docId) {
            int index = -1;
            try {
src/main/java/com/gitblit/MailExecutor.java
@@ -41,9 +41,9 @@
/**
 * The mail executor handles sending email messages asynchronously from queue.
 *
 *
 * @author James Moger
 *
 *
 */
public class MailExecutor implements Runnable {
@@ -90,6 +90,7 @@
        if (!StringUtils.isEmpty(mailUser) && !StringUtils.isEmpty(mailPassword)) {
            // SMTP requires authentication
            session = Session.getInstance(props, new Authenticator() {
                @Override
                protected PasswordAuthentication getPasswordAuthentication() {
                    PasswordAuthentication passwordAuthentication = new PasswordAuthentication(
                            mailUser, mailPassword);
@@ -104,7 +105,7 @@
    /**
     * Indicates if the mail executor can send emails.
     *
     *
     * @return true if the mail executor is ready to send emails
     */
    public boolean isReady() {
@@ -114,7 +115,7 @@
    /**
     * Create a message.
     *
     *
     * @param toAddresses
     * @return a message
     */
@@ -124,7 +125,7 @@
    /**
     * Create a message.
     *
     *
     * @param toAddresses
     * @return a message
     */
@@ -143,7 +144,7 @@
            for (String address : toAddresses) {
                uniques.add(address.toLowerCase());
            }
            Pattern validEmail = Pattern
                    .compile("^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$");
            List<InternetAddress> tos = new ArrayList<InternetAddress>();
@@ -157,7 +158,7 @@
                    } catch (Throwable t) {
                    }
                }
            }
            }
            message.setRecipients(Message.RecipientType.BCC,
                    tos.toArray(new InternetAddress[tos.size()]));
            message.setSentDate(new Date());
@@ -169,7 +170,7 @@
    /**
     * Returns the status of the mail queue.
     *
     *
     * @return true, if the queue is empty
     */
    public boolean hasEmptyQueue() {
@@ -178,7 +179,7 @@
    /**
     * Queue's an email message to be sent.
     *
     *
     * @param message
     * @return true if the message was queued
     */
@@ -213,13 +214,13 @@
                        failures.add(message);
                    }
                }
                // push the failures back onto the queue for the next cycle
                queue.addAll(failures);
            }
        }
    }
    public void sendNow(Message message) throws Exception {
        Transport.send(message);
    }
src/main/java/com/gitblit/PAMUserService.java
@@ -30,7 +30,7 @@
/**
 * Implementation of a PAM user service for Linux/Unix/MacOSX.
 *
 *
 * @author James Moger
 */
public class PAMUserService extends GitblitUserService {
@@ -38,7 +38,7 @@
    private final Logger logger = LoggerFactory.getLogger(PAMUserService.class);
    private IStoredSettings settings;
    public PAMUserService() {
        super();
    }
@@ -52,7 +52,7 @@
        serviceImpl = createUserService(realmFile);
        logger.info("PAM User Service backed by " + serviceImpl.toString());
        // Try to identify the passwd database
        String [] files = { "/etc/shadow", "/etc/master.passwd" };
        File passwdFile = null;
@@ -69,7 +69,7 @@
            logger.error("PAM User Service can not read passwd database {}! PAM authentications may fail!", passwdFile);
        }
    }
    @Override
    public boolean supportsCredentialChanges() {
        return false;
@@ -89,7 +89,7 @@
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    protected AccountType getAccountType() {
        return AccountType.PAM;
@@ -101,7 +101,7 @@
            // local account, bypass PAM authentication
            return super.authenticate(username, password);
        }
        if (CLibrary.libc.getpwnam(username) == null) {
            logger.warn("Can not get PAM passwd for " + username);
            return null;
@@ -136,7 +136,7 @@
        // push the changes to the backing user service
        super.updateUserModel(user);
        return user;
    }
}
src/main/java/com/gitblit/PagesFilter.java
@@ -24,22 +24,22 @@
/**
 * The PagesFilter is an AccessRestrictionFilter which ensures the gh-pages
 * requests for a view-restricted repository are authenticated and authorized.
 *
 *
 * @author James Moger
 *
 *
 */
public class PagesFilter extends AccessRestrictionFilter {
    /**
     * Extract the repository name from the url.
     *
     *
     * @param url
     * @return repository name
     */
    @Override
    protected String extractRepositoryName(String url) {
    protected String extractRepositoryName(String url) {
        // get the repository name from the url by finding a known url suffix
        String repository = "";
        String repository = "";
        Repository r = null;
        int offset = 0;
        while (r == null) {
@@ -52,11 +52,11 @@
            r = GitBlit.self().getRepository(repository, false);
            if (r == null) {
                // try again
                offset = slash + 1;
                offset = slash + 1;
            } else {
                // close the repo
                r.close();
            }
            }
            if (repository.equals(url)) {
                // either only repository in url or no repository found
                break;
@@ -67,7 +67,7 @@
    /**
     * Analyze the url and returns the action of the request.
     *
     *
     * @param cloneUrl
     * @return action of the request
     */
@@ -78,7 +78,7 @@
    /**
     * Determine if a non-existing repository can be created using this filter.
     *
     *
     * @return true if the filter allows repository creation
     */
    @Override
@@ -88,7 +88,7 @@
    /**
     * Determine if the action may be executed on the repository.
     *
     *
     * @param repository
     * @param action
     * @return true if the action may be performed
@@ -97,10 +97,10 @@
    protected boolean isActionAllowed(RepositoryModel repository, String action) {
        return true;
    }
    /**
     * Determine if the repository requires authentication.
     *
     *
     * @param repository
     * @param action
     * @return true if authentication required
@@ -113,14 +113,14 @@
    /**
     * Determine if the user can access the repository and perform the specified
     * action.
     *
     *
     * @param repository
     * @param user
     * @param action
     * @return true if user may execute the action on the repository
     */
    @Override
    protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
    protected boolean canAccess(RepositoryModel repository, UserModel user, String action) {
        return user.canView(repository);
    }
}
src/main/java/com/gitblit/PagesServlet.java
@@ -41,9 +41,9 @@
/**
 * Serves the content of a gh-pages branch.
 *
 *
 * @author James Moger
 *
 *
 */
public class PagesServlet extends HttpServlet {
@@ -57,7 +57,7 @@
    /**
     * Returns an url to this servlet for the specified parameters.
     *
     *
     * @param baseURL
     * @param repository
     * @param path
@@ -73,7 +73,7 @@
    /**
     * Retrieves the specified resource from the gh-pages branch of the
     * repository.
     *
     *
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
src/main/java/com/gitblit/RedmineUserService.java
@@ -1,3 +1,18 @@
/*
 * 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;
import java.io.File;
@@ -73,7 +88,7 @@
    public boolean supportsTeamMembershipChanges() {
        return false;
    }
     @Override
    protected AccountType getAccountType() {
        return AccountType.REDMINE;
@@ -101,12 +116,12 @@
                return null;
            }
        }
        if (StringUtils.isEmpty(jsonString)) {
            logger.error("Received empty authentication response from Redmine");
            return null;
        }
        RedmineCurrent current = null;
        try {
            current = new Gson().fromJson(jsonString, RedmineCurrent.class);
@@ -176,7 +191,7 @@
        InputStreamReader reader = new InputStreamReader(http.getInputStream());
        return IOUtils.toString(reader);
    }
    /**
     * set json response. do NOT invoke from production code.
     * @param json json
src/main/java/com/gitblit/RobotsTxtServlet.java
@@ -27,9 +27,9 @@
/**
 * Handles requests for robots.txt
 *
 *
 * @author James Moger
 *
 *
 */
public class RobotsTxtServlet extends HttpServlet {
@@ -38,7 +38,7 @@
    public RobotsTxtServlet() {
        super();
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, java.io.IOException {
src/main/java/com/gitblit/RpcFilter.java
@@ -30,23 +30,23 @@
/**
 * The RpcFilter is a servlet filter that secures the RpcServlet.
 *
 *
 * The filter extracts the rpc request type from the url and determines if the
 * requested action requires a Basic authentication prompt. If authentication is
 * required and no credentials are stored in the "Authorization" header, then a
 * basic authentication challenge is issued.
 *
 *
 * http://en.wikipedia.org/wiki/Basic_access_authentication
 *
 *
 * @author James Moger
 *
 *
 */
public class RpcFilter extends AuthenticationFilter {
    /**
     * 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)
     */
src/main/java/com/gitblit/RpcServlet.java
@@ -44,9 +44,9 @@
/**
 * Handles remote procedure calls.
 *
 *
 * @author James Moger
 *
 *
 */
public class RpcServlet extends JsonServlet {
@@ -60,7 +60,7 @@
    /**
     * Processes an rpc request.
     *
     *
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
src/main/java/com/gitblit/SalesforceUserService.java
@@ -16,14 +16,15 @@
import com.sforce.ws.ConnectorConfig;
public class SalesforceUserService extends GitblitUserService {
    public static final Logger logger = LoggerFactory
            .getLogger(SalesforceUserService.class);
    public static final Logger logger = LoggerFactory.getLogger(SalesforceUserService.class);
    private IStoredSettings settings;
    @Override
    protected AccountType getAccountType() {
        return AccountType.SALESFORCE;
    }
    @Override
    public void setup(IStoredSettings settings) {
        this.settings = settings;
src/main/java/com/gitblit/SparkleShareInviteServlet.java
@@ -29,9 +29,9 @@
/**
 * Handles requests for Sparkleshare Invites
 *
 *
 * @author James Moger
 *
 *
 */
public class SparkleShareInviteServlet extends HttpServlet {
@@ -40,7 +40,7 @@
    public SparkleShareInviteServlet() {
        super();
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, java.io.IOException {
@@ -55,8 +55,8 @@
    protected void processRequest(javax.servlet.http.HttpServletRequest request,
            javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException,
            java.io.IOException {
            java.io.IOException {
        // extract repo name from request
        String repoUrl = request.getPathInfo().substring(1);
@@ -64,11 +64,11 @@
        if (repoUrl.endsWith(".xml")) {
            repoUrl = repoUrl.substring(0, repoUrl.length() - 4);
        }
        String servletPath =  Constants.GIT_PATH;
        int schemeIndex = repoUrl.indexOf("://") + 3;
        String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex));
        String host = repoUrl.substring(0, repoUrl.indexOf('/', schemeIndex));
        String path = repoUrl.substring(repoUrl.indexOf(servletPath) + servletPath.length());
        String username = null;
        int fetchIndex = repoUrl.indexOf('@');
@@ -85,7 +85,7 @@
            user = UserModel.ANONYMOUS;
            username = "";
        }
        // ensure that the requested repository exists
        RepositoryModel model = GitBlit.self().getRepositoryModel(path);
        if (model == null) {
@@ -93,8 +93,8 @@
            response.getWriter().append(MessageFormat.format("Repository \"{0}\" not found!", path));
            return;
        }
        StringBuilder sb = new StringBuilder();
        StringBuilder sb = new StringBuilder();
        sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        sb.append("<sparkleshare><invite>\n");
        sb.append(MessageFormat.format("<address>{0}</address>\n", host));
src/main/java/com/gitblit/SyndicationFilter.java
@@ -34,15 +34,15 @@
 * The SyndicationFilter is an AuthenticationFilter which ensures that feed
 * requests for projects or view-restricted repositories have proper authentication
 * credentials and are authorized for the requested feed.
 *
 *
 * @author James Moger
 *
 *
 */
public class SyndicationFilter extends AuthenticationFilter {
    /**
     * Extract the repository name from the url.
     *
     *
     * @param url
     * @return repository name
     */
@@ -56,7 +56,7 @@
    /**
     * 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)
     */
@@ -72,7 +72,7 @@
        ProjectModel project = GitBlit.self().getProjectModel(name);
        RepositoryModel model = null;
        if (project == null) {
            // try loading a repository model
            model = GitBlit.self().getRepositoryModel(name);
@@ -84,7 +84,7 @@
                return;
            }
        }
        // Wrap the HttpServletRequest with the AccessRestrictionRequest which
        // overrides the servlet container user principal methods.
        // JGit requires either:
src/main/java/com/gitblit/SyndicationServlet.java
@@ -43,11 +43,11 @@
/**
 * SyndicationServlet generates RSS 2.0 feeds and feed links.
 *
 *
 * Access to this servlet is protected by the SyndicationFilter.
 *
 *
 * @author James Moger
 *
 *
 */
public class SyndicationServlet extends HttpServlet {
@@ -57,7 +57,7 @@
    /**
     * Create a feed link for the specified repository and branch/tag/commit id.
     *
     *
     * @param baseURL
     * @param repository
     *            the repository name
@@ -95,7 +95,7 @@
    /**
     * Determines the appropriate title for a feed.
     *
     *
     * @param repository
     * @param objectId
     * @return title of the feed
@@ -116,7 +116,7 @@
    /**
     * Generates the feed content.
     *
     *
     * @param request
     * @param response
     * @throws javax.servlet.ServletException
@@ -162,12 +162,12 @@
        }
        response.setContentType("application/rss+xml; charset=UTF-8");
        boolean isProjectFeed = false;
        String feedName = null;
        String feedTitle = null;
        String feedDescription = null;
        List<String> repositories = null;
        if (repositoryName.indexOf('/') == -1 && !repositoryName.toLowerCase().endsWith(".git")) {
            // try to find a project
@@ -179,14 +179,14 @@
            if (project != null) {
                isProjectFeed = true;
                repositories = new ArrayList<String>(project.repositories);
                // project feed
                feedName = project.name;
                feedTitle = project.title;
                feedDescription = project.description;
            }
        }
        if (repositories == null) {
            // could not find project, assume this is a repository
            repositories = Arrays.asList(repositoryName);
@@ -214,7 +214,7 @@
            if (repository == null) {
                if (model.isCollectingGarbage) {
                    logger.warn(MessageFormat.format("Temporarily excluding {0} from feed, busy collecting garbage", name));
                }
                }
                continue;
            }
            if (!isProjectFeed) {
@@ -223,7 +223,7 @@
                feedTitle = model.name;
                feedDescription = model.description;
            }
            List<RevCommit> commits;
            if (StringUtils.isEmpty(searchString)) {
                // standard log/history lookup
@@ -248,7 +248,7 @@
                        commit.getFullMessage());
                entry.content = message;
                entry.repository = model.name;
                entry.branch = objectId;
                entry.branch = objectId;
                entry.tags = new ArrayList<String>();
                // add commit id and parent commit ids
@@ -263,18 +263,18 @@
                    for (RefModel ref : refs) {
                        entry.tags.add("ref:" + ref.getName());
                    }
                }
                }
                entries.add(entry);
            }
        }
        // sort & truncate the feed
        Collections.sort(entries);
        if (entries.size() > length) {
            // clip the list
            entries = entries.subList(0, length);
        }
        String feedLink;
        if (isProjectFeed) {
            // project feed
src/main/java/com/gitblit/WebXmlSettings.java
@@ -31,9 +31,9 @@
/**
 * Loads Gitblit settings from the context-parameter values of a web.xml file.
 *
 *
 * @author James Moger
 *
 *
 */
public class WebXmlSettings extends IStoredSettings {
@@ -54,7 +54,7 @@
    public void applyOverrides(File overrideFile) {
        this.overrideFile = overrideFile;
        // apply any web-configured overrides
        if (overrideFile.exists()) {
            try {
src/main/java/com/gitblit/WindowsUserService.java
@@ -36,7 +36,7 @@
/**
 * Implementation of a Windows user service.
 *
 *
 * @author James Moger
 */
public class WindowsUserService extends GitblitUserService {
@@ -44,7 +44,7 @@
    private final Logger logger = LoggerFactory.getLogger(WindowsUserService.class);
    private IStoredSettings settings;
    private IWindowsAuthProvider waffle;
    public WindowsUserService() {
@@ -60,7 +60,7 @@
        serviceImpl = createUserService(realmFile);
        logger.info("Windows User Service backed by " + serviceImpl.toString());
        waffle = new WindowsAuthProviderImpl();
        IWindowsComputer computer = waffle.getCurrentComputer();
        logger.info("      name = " + computer.getComputerName());
@@ -68,7 +68,7 @@
        logger.info("  memberOf = " + computer.getMemberOf());
        //logger.info("  groups     = " + Arrays.asList(computer.getGroups()));
    }
    protected String describeJoinStatus(String value) {
        if ("NetSetupUnknownStatus".equals(value)) {
            return "unknown";
@@ -101,7 +101,7 @@
    public boolean supportsTeamMembershipChanges() {
        return true;
    }
     @Override
    protected AccountType getAccountType() {
        return AccountType.WINDOWS;
@@ -150,7 +150,7 @@
            identity.dispose();
            return null;
        }
        UserModel user = getUserModel(username);
        if (user == null)    // create user object for new authenticated user
            user = new UserModel(username.toLowerCase());
@@ -174,12 +174,12 @@
           for (IWindowsAccount group : identity.getGroups()) {
               groupNames.add(group.getFqn());
        }
        if (groupNames.contains("BUILTIN\\Administrators")) {
            // local administrator
            user.canAdmin = true;
        }
        // TODO consider mapping Windows groups to teams
        // push the changes to the backing user service
@@ -188,7 +188,7 @@
        // cleanup resources
        identity.dispose();
        return user;
    }
}
src/main/java/com/gitblit/authority/AuthorityWorker.java
@@ -35,6 +35,7 @@
        return doRequest();
    }
    @Override
    protected void done() {
        parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        try {
src/main/java/com/gitblit/authority/CertificateStatusRenderer.java
@@ -26,9 +26,9 @@
/**
 * Displays a subscribed icon on the left of the repository name, if there is at
 * least one subscribed branch.
 *
 *
 * @author James Moger
 *
 *
 */
public class CertificateStatusRenderer extends DefaultTableCellRenderer {
@@ -49,6 +49,7 @@
        okIcon = new ImageIcon(getClass().getResource("/bullet_green.png"));
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
src/main/java/com/gitblit/authority/CertificatesTableModel.java
@@ -28,16 +28,16 @@
/**
 * Table model of a list of user certificate models.
 *
 *
 * @author James Moger
 *
 *
 */
public class CertificatesTableModel extends AbstractTableModel {
    private static final long serialVersionUID = 1L;
    UserCertificateModel ucm;
    enum Columns {
        SerialNumber, Status, Reason, Issued, Expires;
@@ -80,11 +80,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        Columns col = Columns.values()[columnIndex];
        switch (col) {
@@ -127,7 +128,7 @@
            if (ucm.getStatus(cert).equals(CertificateStatus.revoked)) {
                RevocationReason r = ucm.getRevocationReason(cert.getSerialNumber());
                return Translation.get("gb." + r.name());
            }
            }
        }
        return null;
    }
@@ -135,7 +136,7 @@
    public X509Certificate get(int modelRow) {
        return ucm.certs.get(modelRow);
    }
    public void setUserCertificateModel(UserCertificateModel ucm) {
        this.ucm = ucm;
        if (ucm == null) {
src/main/java/com/gitblit/authority/DefaultOidsPanel.java
@@ -24,9 +24,9 @@
import com.gitblit.utils.X509Utils.X509Metadata;
public class DefaultOidsPanel extends JPanel {
    private static final long serialVersionUID = 1L;
    private JTextField organizationalUnit;
    private JTextField organization;
    private JTextField locality;
@@ -35,13 +35,13 @@
    public DefaultOidsPanel(X509Metadata metadata) {
        super();
        organizationalUnit = new JTextField(metadata.getOID("OU", ""), 20);
        organization = new JTextField(metadata.getOID("O", ""), 20);
        locality = new JTextField(metadata.getOID("L", ""), 20);
        stateProvince = new JTextField(metadata.getOID("ST", ""), 20);
        countryCode = new JTextField(metadata.getOID("C", ""), 20);
        setLayout(new GridLayout(0, 1, Utils.MARGIN, Utils.MARGIN));
        add(Utils.newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnit));
        add(Utils.newFieldPanel(Translation.get("gb.organization") + " (O)", organization));
@@ -49,7 +49,7 @@
        add(Utils.newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvince));
        add(Utils.newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCode));
    }
    public void update(X509Metadata metadata) {
        metadata.setOID("OU", organizationalUnit.getText());
        metadata.setOID("O", organization.getText());
@@ -57,15 +57,15 @@
        metadata.setOID("ST", stateProvince.getText());
        metadata.setOID("C", countryCode.getText());
    }
    public String getOrganizationalUnit() {
        return organizationalUnit.getText();
    }
    public String getOrganization() {
        return organization.getText();
    }
    public String getLocality() {
        return locality.getText();
    }
src/main/java/com/gitblit/authority/GitblitAuthority.java
@@ -105,32 +105,32 @@
/**
 * Simple GUI tool for administering Gitblit client certificates.
 *
 *
 * @author James Moger
 *
 */
public class GitblitAuthority extends JFrame implements X509Log {
    private static final long serialVersionUID = 1L;
    private final UserCertificateTableModel tableModel;
    private UserCertificatePanel userCertificatePanel;
    private File folder;
    private IStoredSettings gitblitSettings;
    private IUserService userService;
    private String caKeystorePassword;
    private JTable table;
    private int defaultDuration;
    private TableRowSorter<UserCertificateTableModel> defaultSorter;
    private MailExecutor mail;
    private JButton certificateDefaultsButton;
@@ -154,6 +154,7 @@
        }
        final String baseFolder = folder;
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
@@ -172,7 +173,7 @@
        tableModel = new UserCertificateTableModel();
        defaultSorter = new TableRowSorter<UserCertificateTableModel>(tableModel);
    }
    public void initialize(String baseFolder) {
        setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
        setTitle("Gitblit Certificate Authority v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
@@ -187,14 +188,14 @@
            @Override
            public void windowOpened(WindowEvent event) {
            }
        });
        });
        File folder = new File(baseFolder).getAbsoluteFile();
        load(folder);
        setSizeAndPosition();
    }
    private void setSizeAndPosition() {
        String sz = null;
        String pos = null;
@@ -243,14 +244,14 @@
            Utils.showException(GitblitAuthority.this, t);
        }
    }
    private StoredConfig getConfig() throws IOException, ConfigInvalidException {
        File configFile  = new File(folder, X509Utils.CA_CONFIG);
        FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
        config.load();
        return config;
    }
    private IUserService loadUsers(File folder) {
        File file = new File(folder, "gitblit.properties");
        if (!file.exists()) {
@@ -271,11 +272,11 @@
        } else {
            throw new RuntimeException("Unsupported user service: " + us);
        }
        service = new ConfigUserService(FileUtils.resolveParameter(Constants.baseFolder$, folder, us));
        return service;
    }
    private void load(File folder) {
        this.folder = folder;
        this.userService = loadUsers(folder);
@@ -287,7 +288,7 @@
            Map<String, UserCertificateModel> map = new HashMap<String, UserCertificateModel>();
            for (String user : userService.getAllUsernames()) {
                UserModel model = userService.getUserModel(user);
                UserCertificateModel ucm = new UserCertificateModel(model);
                UserCertificateModel ucm = new UserCertificateModel(model);
                map.put(user, ucm);
            }
            File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
@@ -296,8 +297,8 @@
                try {
                    config.load();
                    // replace user certificate model with actual data
                    List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list;
                    for (UserCertificateModel ucm : list) {
                    List<UserCertificateModel> list = UserCertificateConfig.KEY.parse(config).list;
                    for (UserCertificateModel ucm : list) {
                        ucm.user = userService.getUserModel(ucm.user.username);
                        map.put(ucm.user.username, ucm);
                    }
@@ -307,15 +308,15 @@
                    e.printStackTrace();
                }
            }
            tableModel.list = new ArrayList<UserCertificateModel>(map.values());
            Collections.sort(tableModel.list);
            tableModel.fireTableDataChanged();
            Utils.packColumns(table, Utils.MARGIN);
            File caKeystore = new File(folder, X509Utils.CA_KEY_STORE);
            if (!caKeystore.exists()) {
                if (!X509Utils.unlimitedStrength) {
                    // prompt to confirm user understands JCE Standard Strength encryption
                    int res = JOptionPane.showConfirmDialog(GitblitAuthority.this, Translation.get("gb.jceWarning"),
@@ -332,16 +333,16 @@
                        System.exit(1);
                    }
                }
                // show certificate defaults dialog
                // show certificate defaults dialog
                certificateDefaultsButton.doClick();
                // create "localhost" ssl certificate
                prepareX509Infrastructure();
            }
        }
    }
    private boolean prepareX509Infrastructure() {
        if (caKeystorePassword == null) {
            JPasswordField pass = new JPasswordField(10);
@@ -364,7 +365,7 @@
        X509Utils.prepareX509Infrastructure(metadata, folder, this);
        return true;
    }
    private List<X509Certificate> findCerts(File folder, String username) {
        List<X509Certificate> list = new ArrayList<X509Certificate>();
        File userFolder = new File(folder, X509Utils.CERTS + File.separator + username);
@@ -379,7 +380,7 @@
        });
        try {
            CertificateFactory factory = CertificateFactory.getInstance("X.509");
            for (File cert : certs) {
            for (File cert : certs) {
                BufferedInputStream is = new BufferedInputStream(new FileInputStream(cert));
                X509Certificate x509 = (X509Certificate) factory.generateCertificate(is);
                is.close();
@@ -390,16 +391,16 @@
        }
        return list;
    }
    private Container getUI() {
    private Container getUI() {
        userCertificatePanel = new UserCertificatePanel(this) {
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return Utils.INSETS;
            }
            @Override
            public boolean isAllowEmail() {
                return mail.isReady();
@@ -415,12 +416,12 @@
                c.set(Calendar.MILLISECOND, 0);
                return c.getTime();
            }
            @Override
            public boolean saveUser(String username, UserCertificateModel ucm) {
                return userService.updateUserModel(username, ucm.user);
            }
            @Override
            public boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail) {
                if (!prepareX509Infrastructure()) {
@@ -430,9 +431,9 @@
                Date notAfter = metadata.notAfter;
                setMetadataDefaults(metadata);
                metadata.notAfter = notAfter;
                // set user's specified OID values
                UserModel user = ucm.user;
                UserModel user = ucm.user;
                if (!StringUtils.isEmpty(user.organizationalUnit)) {
                    metadata.oids.put("OU", user.organizationalUnit);
                }
@@ -456,21 +457,21 @@
                if (ucm.expires == null || metadata.notAfter.before(ucm.expires)) {
                    ucm.expires = metadata.notAfter;
                }
                updateAuthorityConfig(ucm);
                // refresh user
                ucm.certs = null;
                int selectedIndex = table.getSelectedRow();
                tableModel.fireTableDataChanged();
                table.getSelectionModel().setSelectionInterval(selectedIndex, selectedIndex);
                if (sendEmail) {
                    sendEmail(user, metadata, zip);
                }
                return true;
            }
            @Override
            public boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason) {
                if (!prepareX509Infrastructure()) {
@@ -497,20 +498,20 @@
                    } catch (Exception e) {
                        Utils.showException(GitblitAuthority.this, e);
                    }
                    // refresh user
                    ucm.certs = null;
                    int modelIndex = table.convertRowIndexToModel(table.getSelectedRow());
                    tableModel.fireTableDataChanged();
                    table.getSelectionModel().setSelectionInterval(modelIndex, modelIndex);
                    return true;
                }
                return false;
            }
        };
        table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
        table.setRowSorter(defaultSorter);
        table.setDefaultRenderer(CertificateStatus.class, new CertificateStatusRenderer());
@@ -533,9 +534,9 @@
                userCertificatePanel.setUserCertificateModel(ucm);
            }
        });
        JPanel usersPanel = new JPanel(new BorderLayout()) {
            private static final long serialVersionUID = 1L;
            @Override
@@ -546,10 +547,10 @@
        usersPanel.add(new HeaderPanel(Translation.get("gb.users"), "users_16x16.png"), BorderLayout.NORTH);
        usersPanel.add(new JScrollPane(table), BorderLayout.CENTER);
        usersPanel.setMinimumSize(new Dimension(400, 10));
        certificateDefaultsButton = new JButton(new ImageIcon(getClass().getResource("/settings_16x16.png")));
        certificateDefaultsButton.setFocusable(false);
        certificateDefaultsButton.setToolTipText(Translation.get("gb.newCertificateDefaults"));
        certificateDefaultsButton.setToolTipText(Translation.get("gb.newCertificateDefaults"));
        certificateDefaultsButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
@@ -567,6 +568,7 @@
                    certificateConfig.update(metadata);
                }
                InputVerifier verifier = new InputVerifier() {
                    @Override
                    public boolean verify(JComponent comp) {
                        boolean returnValue;
                        JTextField textField = (JTextField) comp;
@@ -591,18 +593,18 @@
                validityTF.setText("" + certificateConfig.duration);
                JPanel validityPanel = Utils.newFieldPanel(Translation.get("gb.validity"),
                        validityTF, Translation.get("gb.duration.days").replace("{0}",  "").trim());
                JPanel p1 = new JPanel(new GridLayout(0, 1, 5, 2));
                p1.add(siteNamePanel);
                p1.add(validityPanel);
                DefaultOidsPanel oids = new DefaultOidsPanel(metadata);
                JPanel panel = new JPanel(new BorderLayout());
                panel.add(p1, BorderLayout.NORTH);
                panel.add(oids, BorderLayout.CENTER);
                int result = JOptionPane.showConfirmDialog(GitblitAuthority.this,
                int result = JOptionPane.showConfirmDialog(GitblitAuthority.this,
                        panel, Translation.get("gb.newCertificateDefaults"), JOptionPane.OK_CANCEL_OPTION,
                        JOptionPane.QUESTION_MESSAGE, new ImageIcon(getClass().getResource("/settings_32x32.png")));
                if (result == JOptionPane.OK_OPTION) {
@@ -611,7 +613,7 @@
                        certificateConfig.duration = Integer.parseInt(validityTF.getText());
                        certificateConfig.store(config, metadata);
                        config.save();
                        Map<String, String> updates = new HashMap<String, String>();
                        updates.put(Keys.web.siteName, siteNameTF.getText());
                        gitblitSettings.saveSettings(updates);
@@ -621,10 +623,10 @@
                }
            }
        });
        newSSLCertificate = new JButton(new ImageIcon(getClass().getResource("/rosette_16x16.png")));
        newSSLCertificate.setFocusable(false);
        newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate"));
        newSSLCertificate.setToolTipText(Translation.get("gb.newSSLCertificate"));
        newSSLCertificate.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
@@ -638,7 +640,7 @@
                final Date expires = dialog.getExpiration();
                final String hostname = dialog.getHostname();
                final boolean serveCertificate = dialog.isServeCertificate();
                AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
                    @Override
@@ -646,12 +648,12 @@
                        if (!prepareX509Infrastructure()) {
                            return false;
                        }
                        // read CA private key and certificate
                        File caKeystoreFile = new File(folder, X509Utils.CA_KEY_STORE);
                        PrivateKey caPrivateKey = X509Utils.getPrivateKey(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword);
                        X509Certificate caCert = X509Utils.getCertificate(X509Utils.CA_ALIAS, caKeystoreFile, caKeystorePassword);
                        // generate new SSL certificate
                        X509Metadata metadata = new X509Metadata(hostname, caKeystorePassword);
                        setMetadataDefaults(metadata);
@@ -671,24 +673,24 @@
                    @Override
                    protected void onSuccess() {
                        if (serveCertificate) {
                            JOptionPane.showMessageDialog(GitblitAuthority.this,
                            JOptionPane.showMessageDialog(GitblitAuthority.this,
                                    MessageFormat.format(Translation.get("gb.sslCertificateGeneratedRestart"), hostname),
                                    Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
                        } else {
                            JOptionPane.showMessageDialog(GitblitAuthority.this,
                            JOptionPane.showMessageDialog(GitblitAuthority.this,
                                MessageFormat.format(Translation.get("gb.sslCertificateGenerated"), hostname),
                                Translation.get("gb.newSSLCertificate"), JOptionPane.INFORMATION_MESSAGE);
                        }
                    }
                };
                worker.execute();
            }
        });
        JButton emailBundle = new JButton(new ImageIcon(getClass().getResource("/mail_16x16.png")));
        emailBundle.setFocusable(false);
        emailBundle.setToolTipText(Translation.get("gb.emailCertificateBundle"));
        emailBundle.setToolTipText(Translation.get("gb.emailCertificateBundle"));
        emailBundle.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
@@ -705,7 +707,7 @@
                if (!zip.exists()) {
                    return;
                }
                AuthorityWorker worker = new AuthorityWorker(GitblitAuthority.this) {
                    @Override
                    protected Boolean doRequest() throws IOException {
@@ -723,15 +725,15 @@
                        JOptionPane.showMessageDialog(GitblitAuthority.this, MessageFormat.format(Translation.get("gb.clientCertificateBundleSent"),
                                ucm.user.getDisplayName()));
                    }
                };
                worker.execute();
                worker.execute();
            }
        });
        JButton logButton = new JButton(new ImageIcon(getClass().getResource("/script_16x16.png")));
        logButton.setFocusable(false);
        logButton.setToolTipText(Translation.get("gb.log"));
        logButton.setToolTipText(Translation.get("gb.log"));
        logButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
@@ -745,19 +747,21 @@
                }
            }
        });
        final JTextField filterTextfield = new JTextField(15);
        filterTextfield.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                filterUsers(filterTextfield.getText());
            }
        });
        filterTextfield.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                filterUsers(filterTextfield.getText());
            }
        });
        JToolBar buttonControls = new JToolBar(JToolBar.HORIZONTAL);
        buttonControls.setFloatable(false);
        buttonControls.add(certificateDefaultsButton);
@@ -768,17 +772,17 @@
        JPanel userControls = new JPanel(new FlowLayout(FlowLayout.RIGHT, Utils.MARGIN, Utils.MARGIN));
        userControls.add(new JLabel(Translation.get("gb.filter")));
        userControls.add(filterTextfield);
        JPanel topPanel = new JPanel(new BorderLayout(0, 0));
        topPanel.add(buttonControls, BorderLayout.WEST);
        topPanel.add(userControls, BorderLayout.EAST);
        JPanel leftPanel = new JPanel(new BorderLayout());
        leftPanel.add(topPanel, BorderLayout.NORTH);
        leftPanel.add(usersPanel, BorderLayout.CENTER);
        userCertificatePanel.setMinimumSize(new Dimension(375, 10));
        JLabel statusLabel = new JLabel();
        statusLabel.setHorizontalAlignment(SwingConstants.RIGHT);
        if (X509Utils.unlimitedStrength) {
@@ -786,9 +790,10 @@
        } else {
            statusLabel.setText("JCE Standard Encryption Policy");
        }
        JPanel root = new JPanel(new BorderLayout()) {
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return Utils.INSETS;
            }
@@ -799,7 +804,7 @@
        root.add(statusLabel, BorderLayout.SOUTH);
        return root;
    }
    private void filterUsers(final String fragment) {
        table.clearSelection();
        userCertificatePanel.setUserCertificateModel(null);
@@ -808,6 +813,7 @@
            return;
        }
        RowFilter<UserCertificateTableModel, Object> containsFilter = new RowFilter<UserCertificateTableModel, Object>() {
            @Override
            public boolean include(Entry<? extends UserCertificateTableModel, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
@@ -822,7 +828,7 @@
        sorter.setRowFilter(containsFilter);
        table.setRowSorter(sorter);
    }
    @Override
    public void log(String message) {
        BufferedWriter writer = null;
@@ -842,7 +848,7 @@
            }
        }
    }
    private boolean sendEmail(UserModel user, X509Metadata metadata, File zip) {
        // send email
        try {
@@ -879,13 +885,13 @@
        }
        return false;
    }
    private void setMetadataDefaults(X509Metadata metadata) {
        metadata.serverHostname = gitblitSettings.getString(Keys.web.siteName, Constants.NAME);
        if (StringUtils.isEmpty(metadata.serverHostname)) {
            metadata.serverHostname = Constants.NAME;
        }
        // set default values from config file
        File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
        FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
@@ -899,7 +905,7 @@
            certificateConfig.update(metadata);
        }
    }
    private void updateAuthorityConfig(UserCertificateModel ucm) {
        File certificatesConfigFile = new File(folder, X509Utils.CA_CONFIG);
        FileBasedConfig config = new FileBasedConfig(certificatesConfigFile, FS.detect());
src/main/java/com/gitblit/authority/Launcher.java
@@ -37,9 +37,9 @@
/**
 * Downloads dependencies and launches Gitblit Authority.
 *
 *
 * @author James Moger
 *
 *
 */
public class Launcher {
@@ -53,10 +53,10 @@
    public static void main(String[] args) {
        final SplashScreen splash = SplashScreen.getSplashScreen();
        File libFolder = new File("ext");
        List<File> jars = findJars(libFolder.getAbsoluteFile());
        // sort the jars by name and then reverse the order so the newer version
        // of the library gets loaded in the event that this is an upgrade
        Collections.sort(jars);
@@ -69,7 +69,7 @@
            }
        }
        updateSplash(splash, Translation.get("gb.starting") + " Gitblit Authority...");
        GitblitAuthority.main(args);
    }
@@ -80,12 +80,13 @@
        }
        try {
            EventQueue.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    Graphics2D g = splash.createGraphics();
                    if (g != null) {
                        // Splash is 320x120
                        FontMetrics fm = g.getFontMetrics();
                        // paint startup status
                        g.setColor(Color.darkGray);
                        int h = fm.getHeight() + fm.getMaxDescent();
@@ -98,7 +99,7 @@
                        g.setColor(Color.WHITE);
                        int xw = fm.stringWidth(string);
                        g.drawString(string, x + ((w - xw) / 2), y - 5);
                        // paint version
                        String ver = "v" + Constants.getVersion();
                        int vw = g.getFontMetrics().stringWidth(ver);
@@ -112,7 +113,7 @@
            t.printStackTrace();
        }
    }
    public static List<File> findJars(File folder) {
        List<File> jars = new ArrayList<File>();
        if (folder.exists()) {
@@ -137,7 +138,7 @@
    /**
     * Adds a file to the classpath
     *
     *
     * @param f
     *            the file to be added
     * @throws IOException
src/main/java/com/gitblit/authority/NewCertificateConfig.java
@@ -26,11 +26,12 @@
/**
 * Certificate config file parser.
 *
 *
 * @author James Moger
 */
public class NewCertificateConfig {
        public static final SectionParser<NewCertificateConfig> KEY = new SectionParser<NewCertificateConfig>() {
            @Override
            public NewCertificateConfig parse(final Config cfg) {
                return new NewCertificateConfig(cfg);
            }
@@ -41,18 +42,18 @@
        public String L;
        public String ST;
        public String C;
        public int duration;
        private NewCertificateConfig(final Config c) {
            duration = c.getInt("new",  null, "duration", 0);
            OU = c.getString("new", null, "organizationalUnit");
            O = c.getString("new", null, "organization");
            L = c.getString("new", null, "locality");
            ST = c.getString("new", null, "stateProvince");
            C = c.getString("new", null, "countryCode");
            C = c.getString("new", null, "countryCode");
        }
        public void update(X509Metadata metadata) {
            update(metadata, "OU", OU);
            update(metadata, "O", O);
@@ -63,13 +64,13 @@
                metadata.notAfter = new Date(System.currentTimeMillis() + duration*TimeUtils.ONEDAY);
            }
        }
        private void update(X509Metadata metadata, String oid, String value) {
            if (!StringUtils.isEmpty(value)) {
                metadata.oids.put(oid, value);
            }
        }
        public void store(Config c, X509Metadata metadata) {
            store(c, "new", "organizationalUnit", metadata.getOID("OU", null));
            store(c, "new", "organization", metadata.getOID("O", null));
@@ -82,7 +83,7 @@
                c.setInt("new", null, "duration", duration);
            }
        }
        private void store(Config c, String section, String name, String value) {
            if (StringUtils.isEmpty(value)) {
                c.unset(section, null, name);
src/main/java/com/gitblit/authority/NewClientCertificateDialog.java
@@ -45,7 +45,7 @@
public class NewClientCertificateDialog extends JDialog {
    private static final long serialVersionUID = 1L;
    JDateChooser expirationDate;
    JPasswordField pw1;
    JPasswordField pw2;
@@ -55,47 +55,48 @@
    public NewClientCertificateDialog(Frame owner, String displayname, Date defaultExpiration, boolean allowEmail) {
        super(owner);
        setTitle(Translation.get("gb.newCertificate"));
        JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
        JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return Utils.INSETS;
            }
        };
        expirationDate = new JDateChooser(defaultExpiration);
        pw1 = new JPasswordField(20);
        pw2 = new JPasswordField(20);
        hint = new JTextField(20);
        sendEmail = new JCheckBox(Translation.get("gb.sendEmail"));
        JPanel panel = new JPanel(new GridLayout(0, 2, Utils.MARGIN, Utils.MARGIN));
        panel.add(new JLabel(Translation.get("gb.expires")));
        panel.add(expirationDate);
        panel.add(new JLabel(Translation.get("gb.password")));
        panel.add(pw1);
        panel.add(new JLabel(Translation.get("gb.confirmPassword")));
        panel.add(pw2);
        panel.add(new JLabel(Translation.get("gb.passwordHint")));
        panel.add(hint);
        if (allowEmail) {
            panel.add(new JLabel(""));
            panel.add(sendEmail);
        }
        JButton ok = new JButton(Translation.get("gb.ok"));
        ok.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (validateInputs()) {
                    isCanceled = false;
@@ -105,34 +106,35 @@
        });
        JButton cancel = new JButton(Translation.get("gb.cancel"));
        cancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                isCanceled = true;
                setVisible(false);
            }
        });
        JPanel controls = new JPanel();
        controls.add(ok);
        controls.add(cancel);
        JTextArea message = new JTextArea(Translation.get("gb.newClientCertificateMessage"));
        message.setLineWrap(true);
        message.setWrapStyleWord(true);
        message.setEditable(false);
        message.setRows(6);
        message.setPreferredSize(new Dimension(300, 100));
        content.add(new JScrollPane(message), BorderLayout.CENTER);
        content.add(panel, BorderLayout.NORTH);
        content.add(controls, BorderLayout.SOUTH);
        getContentPane().add(new HeaderPanel(Translation.get("gb.newCertificate") + " (" + displayname + ")", "rosette_16x16.png"), BorderLayout.NORTH);
        getContentPane().add(content, BorderLayout.CENTER);
        pack();
        setLocationRelativeTo(owner);
    }
    private boolean validateInputs() {
        if (getExpiration().getTime() < System.currentTimeMillis()) {
            // expires before now
@@ -154,23 +156,23 @@
        }
        return true;
    }
    public String getPassword() {
        return new String(pw1.getPassword());
    }
    public String getPasswordHint() {
        return hint.getText();
    }
    public Date getExpiration() {
        return expirationDate.getDate();
    }
    public boolean sendEmail() {
        return sendEmail.isSelected();
    }
    public boolean isCanceled() {
        return isCanceled;
    }
src/main/java/com/gitblit/authority/NewSSLCertificateDialog.java
@@ -39,7 +39,7 @@
public class NewSSLCertificateDialog extends JDialog {
    private static final long serialVersionUID = 1L;
    JDateChooser expirationDate;
    JTextField hostname;
    JCheckBox serveCertificate;
@@ -47,36 +47,37 @@
    public NewSSLCertificateDialog(Frame owner, Date defaultExpiration) {
        super(owner);
        setTitle(Translation.get("gb.newSSLCertificate"));
        JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
        JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return Utils.INSETS;
            }
        };
        expirationDate = new JDateChooser(defaultExpiration);
        hostname = new JTextField(20);
        serveCertificate = new JCheckBox(Translation.get("gb.serveCertificate"), true);
        JPanel panel = new JPanel(new GridLayout(0, 2, Utils.MARGIN, Utils.MARGIN));
        panel.add(new JLabel(Translation.get("gb.hostname")));
        panel.add(hostname);
        panel.add(new JLabel(Translation.get("gb.expires")));
        panel.add(expirationDate);
        panel.add(new JLabel(""));
        panel.add(serveCertificate);
        JButton ok = new JButton(Translation.get("gb.ok"));
        ok.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (validateInputs()) {
                    isCanceled = false;
@@ -86,26 +87,27 @@
        });
        JButton cancel = new JButton(Translation.get("gb.cancel"));
        cancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                isCanceled = true;
                setVisible(false);
            }
        });
        JPanel controls = new JPanel();
        controls.add(ok);
        controls.add(cancel);
        content.add(panel, BorderLayout.CENTER);
        content.add(controls, BorderLayout.SOUTH);
        getContentPane().add(new HeaderPanel(Translation.get("gb.newSSLCertificate"), "rosette_16x16.png"), BorderLayout.NORTH);
        getContentPane().add(content, BorderLayout.CENTER);
        pack();
        setLocationRelativeTo(owner);
    }
    private boolean validateInputs() {
        if (getExpiration().getTime() < System.currentTimeMillis()) {
            // expires before now
@@ -121,11 +123,11 @@
        }
        return true;
    }
    public String getHostname() {
        return hostname.getText();
    }
    public Date getExpiration() {
        return expirationDate.getDate();
    }
src/main/java/com/gitblit/authority/RequestFocusListener.java
@@ -34,7 +34,7 @@
 *  allows you to specify a boolean value of false to prevent the
 *  AncestorListener from being removed when the event is generated. This will
 *  allow you to reuse the listener each time the event is generated.
 *
 *
 *  @author Rob Camick
 */
public class RequestFocusListener implements AncestorListener
src/main/java/com/gitblit/authority/UserCertificateConfig.java
@@ -30,35 +30,36 @@
/**
 * User certificate config section parser.
 *
 *
 * @author James Moger
 */
public class UserCertificateConfig {
    public static final SectionParser<UserCertificateConfig> KEY = new SectionParser<UserCertificateConfig>() {
        public UserCertificateConfig parse(final Config cfg) {
        @Override
        public UserCertificateConfig parse(final Config cfg) {
            return new UserCertificateConfig(cfg);
        }
    };
    public final List<UserCertificateModel> list;
    private UserCertificateConfig(final Config c) {
        SimpleDateFormat df = new SimpleDateFormat(Constants.ISO8601);
        list = new ArrayList<UserCertificateModel>();
        list = new ArrayList<UserCertificateModel>();
        for (String username : c.getSubsections("user")) {
            UserCertificateModel uc = new UserCertificateModel(new UserModel(username));
            try {
                uc.expires = df.parse(c.getString("user", username, "expires"));
            } catch (ParseException e) {
                LoggerFactory.getLogger(UserCertificateConfig.class).error("Failed to parse date!", e);
            } catch (NullPointerException e) {
            } catch (NullPointerException e) {
            }
            uc.notes = c.getString("user", username, "notes");
            uc.revoked = new ArrayList<String>(Arrays.asList(c.getStringList("user", username, "revoked")));
            uc.revoked = new ArrayList<String>(Arrays.asList(c.getStringList("user", username, "revoked")));
            list.add(uc);
        }
    }
    public UserCertificateModel getUserCertificateModel(String username) {
        for (UserCertificateModel ucm : list) {
            if (ucm.user.username.equalsIgnoreCase(username)) {
src/main/java/com/gitblit/authority/UserCertificateModel.java
@@ -41,7 +41,7 @@
        public UserCertificateModel(UserModel user) {
            this.user = user;
        }
        public void update(Config config) {
            if (expires == null) {
                config.unset("user",  user.username, "expires");
@@ -65,7 +65,7 @@
        public int compareTo(UserCertificateModel o) {
            return user.compareTo(o.user);
        }
        public void revoke(BigInteger serial, RevocationReason reason) {
            if (revoked == null) {
                revoked = new ArrayList<String>();
@@ -82,7 +82,7 @@
                }
            }
        }
        public boolean isRevoked(BigInteger serial) {
            return isRevoked(serial.toString());
        }
@@ -99,7 +99,7 @@
            }
            return false;
        }
        public RevocationReason getRevocationReason(BigInteger serial) {
            try {
                String sn = serial + ":";
@@ -114,7 +114,7 @@
            }
            return RevocationReason.unspecified;
        }
        public CertificateStatus getStatus() {
            if (expires == null) {
                return CertificateStatus.unknown;
@@ -140,11 +140,11 @@
            }
            return CertificateStatus.ok;
        }
        private boolean isExpiring(Date date) {
            return (date.getTime() - System.currentTimeMillis()) <= TimeUtils.ONEDAY * 30;
        }
        private boolean isExpired(Date date) {
            return date.getTime() < System.currentTimeMillis();
        }
src/main/java/com/gitblit/authority/UserCertificatePanel.java
@@ -46,13 +46,13 @@
public abstract class UserCertificatePanel extends JPanel {
    private static final long serialVersionUID = 1L;
    private Frame owner;
    private UserCertificateModel ucm;
    private UserOidsPanel oidsPanel;
    private CertificatesTableModel tableModel;
    private JButton saveUserButton;
@@ -60,27 +60,28 @@
    private JButton editUserButton;
    private JButton newCertificateButton;
    private JButton revokeCertificateButton;
    private JTable table;
    public UserCertificatePanel(Frame owner) {
        super(new BorderLayout());
        this.owner = owner;
        oidsPanel = new UserOidsPanel();
        JPanel fp = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
        fp.add(oidsPanel, BorderLayout.NORTH);
        JPanel fieldsPanel = new JPanel(new BorderLayout());
        fieldsPanel.add(new HeaderPanel(Translation.get("gb.properties"), "vcard_16x16.png"), BorderLayout.NORTH);
        fieldsPanel.add(fp, BorderLayout.CENTER);
        saveUserButton = new JButton(Translation.get("gb.save"));
        saveUserButton.setEnabled(false);
        saveUserButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setEditable(false);
                String username = ucm.user.username;
@@ -88,22 +89,23 @@
                saveUser(username, ucm);
            }
        });
        editUserButton = new JButton(Translation.get("gb.edit"));
        editUserButton.setEnabled(false);
        editUserButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setEditable(true);
            }
        });
        JPanel userControls = new JPanel(new FlowLayout(FlowLayout.LEFT));
        userControls.add(editUserButton);
        userControls.add(saveUserButton);
        fieldsPanel.add(userControls, BorderLayout.SOUTH);
        JPanel certificatesPanel = new JPanel(new BorderLayout());
        certificatesPanel.add(new HeaderPanel(Translation.get("gb.certificates"), "rosette_16x16.png"), BorderLayout.NORTH);
        certificatesPanel.add(new HeaderPanel(Translation.get("gb.certificates"), "rosette_16x16.png"), BorderLayout.NORTH);
        tableModel = new CertificatesTableModel();
        table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
        table.setRowSorter(new TableRowSorter<CertificatesTableModel>(tableModel));
@@ -125,21 +127,23 @@
            }
        });
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                if (e.getClickCount() == 2) {
                    int row = table.rowAtPoint(e.getPoint());
                    int modelIndex = table.convertRowIndexToModel(row);
                    X509Certificate cert = tableModel.get(modelIndex);
                    X509CertificateViewer viewer = new X509CertificateViewer(UserCertificatePanel.this.owner, cert);
                    X509CertificateViewer viewer = new X509CertificateViewer(UserCertificatePanel.this.owner, cert);
                    viewer.setVisible(true);
                }
            }
        });
        certificatesPanel.add(new JScrollPane(table), BorderLayout.CENTER);
        newCertificateButton = new JButton(Translation.get("gb.newCertificate"));
        newCertificateButton.setEnabled(false);
        newCertificateButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    if (saveUserButton.isEnabled()) {
@@ -149,7 +153,7 @@
                        oidsPanel.updateUser(ucm);
                        saveUser(username, ucm);
                    }
                    NewClientCertificateDialog dialog = new NewClientCertificateDialog(UserCertificatePanel.this.owner,
                            ucm.user.getDisplayName(), getDefaultExpiration(), isAllowEmail());
                    dialog.setModal(true);
@@ -162,7 +166,7 @@
                    final UserModel user = ucm.user;
                    final X509Metadata metadata = new X509Metadata(user.username, dialog.getPassword());
                    metadata.userDisplayname = user.getDisplayName();
                    metadata.emailAddress = user.emailAddress;
                    metadata.emailAddress = user.emailAddress;
                    metadata.passwordHint = dialog.getPasswordHint();
                    metadata.notAfter = dialog.getExpiration();
@@ -174,21 +178,22 @@
                        @Override
                        protected void onSuccess() {
                            JOptionPane.showMessageDialog(UserCertificatePanel.this.owner,
                            JOptionPane.showMessageDialog(UserCertificatePanel.this.owner,
                                    MessageFormat.format(Translation.get("gb.clientCertificateGenerated"), user.getDisplayName()),
                                    Translation.get("gb.newCertificate"), JOptionPane.INFORMATION_MESSAGE);
                        }
                    };
                    worker.execute();
                    worker.execute();
                } catch (Exception x) {
                    Utils.showException(UserCertificatePanel.this, x);
                }
            }
        });
        revokeCertificateButton = new JButton(Translation.get("gb.revokeCertificate"));
        revokeCertificateButton.setEnabled(false);
        revokeCertificateButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    int row = table.getSelectedRow();
@@ -197,12 +202,12 @@
                    }
                    int modelIndex = table.convertRowIndexToModel(row);
                    final X509Certificate cert = tableModel.get(modelIndex);
                    String [] choices = new String[RevocationReason.reasons.length];
                    for (int i = 0; i < choices.length; i++) {
                        choices[i] = Translation.get("gb." + RevocationReason.reasons[i].name());
                    }
                    Object choice = JOptionPane.showInputDialog(UserCertificatePanel.this.owner,
                            Translation.get("gb.revokeCertificateReason"), Translation.get("gb.revokeCertificate"),
                            JOptionPane.PLAIN_MESSAGE, new ImageIcon(getClass().getResource("/rosette_32x32.png")), choices, Translation.get("gb.unspecified"));
@@ -224,7 +229,7 @@
                        } else {
                            // determine new expires date for user
                            Date newExpires = null;
                            for (X509Certificate c : ucm.certs) {
                            for (X509Certificate c : ucm.certs) {
                                if (!c.equals(cert)) {
                                    if (!ucm.isRevoked(c.getSerialNumber())) {
                                        if (newExpires == null || c.getNotAfter().after(newExpires)) {
@@ -235,7 +240,7 @@
                            }
                            ucm.expires = newExpires;
                        }
                        AuthorityWorker worker = new AuthorityWorker(UserCertificatePanel.this.owner) {
                            @Override
@@ -245,11 +250,11 @@
                            @Override
                            protected void onSuccess() {
                                JOptionPane.showMessageDialog(UserCertificatePanel.this.owner,
                                JOptionPane.showMessageDialog(UserCertificatePanel.this.owner,
                                        MessageFormat.format(Translation.get("gb.certificateRevoked"), cert.getSerialNumber(), cert.getIssuerDN().getName()),
                                        Translation.get("gb.revokeCertificate"), JOptionPane.INFORMATION_MESSAGE);
                            }
                        };
                        worker.execute();
                    }
@@ -258,40 +263,40 @@
                }
            }
        });
        JPanel certificateControls = new JPanel(new FlowLayout(FlowLayout.LEFT));
        certificateControls.add(newCertificateButton);
        certificateControls.add(revokeCertificateButton);
        certificatesPanel.add(certificateControls, BorderLayout.SOUTH);
        add(fieldsPanel, BorderLayout.NORTH);
        add(certificatesPanel, BorderLayout.CENTER);
        setEditable(false);
    }
    public void setUserCertificateModel(UserCertificateModel ucm) {
        this.ucm = ucm;
        setEditable(false);
        oidsPanel.setUserCertificateModel(ucm);
        tableModel.setUserCertificateModel(ucm);
        tableModel.fireTableDataChanged();
        Utils.packColumns(table, Utils.MARGIN);
    }
    public void setEditable(boolean editable) {
        oidsPanel.setEditable(editable);
        editUserButton.setEnabled(!editable && ucm != null);
        saveUserButton.setEnabled(editable && ucm != null);
        newCertificateButton.setEnabled(ucm != null);
        revokeCertificateButton.setEnabled(false);
    }
    public abstract Date getDefaultExpiration();
    public abstract boolean isAllowEmail();
    public abstract boolean saveUser(String username, UserCertificateModel ucm);
    public abstract boolean newCertificate(UserCertificateModel ucm, X509Metadata metadata, boolean sendEmail);
    public abstract boolean revoke(UserCertificateModel ucm, X509Certificate cert, RevocationReason reason);
src/main/java/com/gitblit/authority/UserCertificateTableModel.java
@@ -26,9 +26,9 @@
/**
 * Table model of a list of user certificate models.
 *
 *
 * @author James Moger
 *
 *
 */
public class UserCertificateTableModel extends AbstractTableModel {
@@ -82,11 +82,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        Columns col = Columns.values()[columnIndex];
        switch (col) {
src/main/java/com/gitblit/authority/UserOidsPanel.java
@@ -23,9 +23,9 @@
import com.gitblit.client.Translation;
public class UserOidsPanel extends JPanel {
    private static final long serialVersionUID = 1L;
    private JTextField displayname;
    private JTextField username;
    private JTextField emailAddress;
@@ -37,7 +37,7 @@
    public UserOidsPanel() {
        super();
        displayname = new JTextField(20);
        username = new JTextField(20);
        username.setEditable(false);
@@ -47,7 +47,7 @@
        locality = new JTextField(20);
        stateProvince = new JTextField(20);
        countryCode = new JTextField(20);
        setLayout(new GridLayout(0, 1, Utils.MARGIN, Utils.MARGIN));
        add(Utils.newFieldPanel(Translation.get("gb.displayName"), displayname));
        add(Utils.newFieldPanel(Translation.get("gb.username") + " (CN)", username));
@@ -58,7 +58,7 @@
        add(Utils.newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvince));
        add(Utils.newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCode));
    }
    public void setUserCertificateModel(UserCertificateModel ucm) {
        setEditable(false);
        displayname.setText(ucm == null ? "" : ucm.user.getDisplayName());
@@ -70,7 +70,7 @@
        stateProvince.setText(ucm == null ? "" : ucm.user.stateProvince);
        countryCode.setText(ucm == null ? "" : ucm.user.countryCode);
    }
    public void setEditable(boolean editable) {
        displayname.setEditable(editable);
//        username.setEditable(editable);
@@ -81,7 +81,7 @@
        stateProvince.setEditable(editable);
        countryCode.setEditable(editable);
    }
    protected void updateUser(UserCertificateModel ucm) {
        ucm.user.displayName = displayname.getText();
        ucm.user.username = username.getText();
src/main/java/com/gitblit/authority/Utils.java
@@ -1,3 +1,18 @@
/*
 * 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.authority;
import java.awt.Color;
@@ -26,7 +41,7 @@
import com.gitblit.utils.StringUtils;
public class Utils {
    public final static int LABEL_WIDTH = 175;
    public final static int MARGIN = 5;
@@ -49,11 +64,11 @@
                new DateCellRenderer(datePattern, Color.orange.darker()));
        return table;
    }
    public static JPanel newFieldPanel(String label, Component c) {
        return newFieldPanel(label, c, null);
    }
    public static JPanel newFieldPanel(String label, Component c, String trailingLabel) {
        JLabel jlabel = new JLabel(label);
        jlabel.setPreferredSize(new Dimension(Utils.LABEL_WIDTH, 20));
@@ -61,11 +76,11 @@
        panel.add(jlabel);
        panel.add(c);
        if (!StringUtils.isEmpty(trailingLabel)) {
            panel.add(new JLabel(trailingLabel));
            panel.add(new JLabel(trailingLabel));
        }
        return panel;
    }
    public static void showException(Component c, Throwable t) {
        StringWriter writer = new StringWriter();
        t.printStackTrace(new PrintWriter(writer));
@@ -81,7 +96,7 @@
        JOptionPane.showMessageDialog(c, jsp, Translation.get("gb.error"),
                JOptionPane.ERROR_MESSAGE);
    }
    public static void packColumns(JTable table, int margin) {
        for (int c = 0; c < table.getColumnCount(); c++) {
            packColumn(table, c, 4);
src/main/java/com/gitblit/authority/X509CertificateViewer.java
@@ -41,24 +41,24 @@
public class X509CertificateViewer extends JDialog {
    private static final long serialVersionUID = 1L;
    public X509CertificateViewer(Frame owner, X509Certificate cert) {
        super(owner);
        setTitle(Translation.get("gb.viewCertificate"));
        JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
        JPanel content = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)) {
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return Utils.INSETS;
            }
        };
        DateFormat df = DateFormat.getDateTimeInstance();
        int l1 = 15;
        int l2 = 25;
        int l3 = 45;
@@ -82,26 +82,27 @@
        }
        content.add(panel, BorderLayout.CENTER);
        JButton ok = new JButton(Translation.get("gb.ok"));
        ok.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                setVisible(false);
            }
        });
        JPanel controls = new JPanel();
        controls.add(ok);
        content.add(controls, BorderLayout.SOUTH);
        getContentPane().add(new HeaderPanel(Translation.get("gb.certificate"), "rosette_16x16.png"), BorderLayout.NORTH);
        getContentPane().add(content, BorderLayout.CENTER);
        pack();
        setLocationRelativeTo(owner);
    }
    private JPanel newField(String label, String value, int cols) {
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 2*Utils.MARGIN, 0));
        JLabel lbl = new JLabel(label);
@@ -114,7 +115,7 @@
        panel.add(tf);
        return panel;
    }
    private String fingerprint(String value) {
        value = value.toUpperCase();
        StringBuilder sb = new StringBuilder();
src/main/java/com/gitblit/client/BooleanCellRenderer.java
@@ -25,9 +25,9 @@
/**
 * Boolean checkbox cell renderer.
 *
 *
 * @author James Moger
 *
 *
 */
public class BooleanCellRenderer extends JCheckBox implements TableCellRenderer, Serializable {
@@ -39,6 +39,7 @@
        setHorizontalAlignment(SwingConstants.CENTER);
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        if (value instanceof Boolean) {
src/main/java/com/gitblit/client/BranchRenderer.java
@@ -26,9 +26,9 @@
/**
 * Branch renderer displays refs/heads and refs/remotes in a color similar to
 * the site.
 *
 *
 * @author James Moger
 *
 *
 */
public class BranchRenderer extends DefaultTableCellRenderer implements ListCellRenderer {
@@ -37,9 +37,10 @@
    private static final String R_HEADS = "refs/heads/";
    private static final String R_REMOTES = "refs/remotes/";
    private static final String R_CHANGES = "refs/changes/";
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
src/main/java/com/gitblit/client/ClosableTabComponent.java
@@ -46,6 +46,7 @@
    private static final long serialVersionUID = 1L;
    private static final MouseListener BUTTON_MOUSE_LISTENER = new MouseAdapter() {
        @Override
        public void mouseEntered(MouseEvent e) {
            Component component = e.getComponent();
            if (component instanceof AbstractButton) {
@@ -54,6 +55,7 @@
            }
        }
        @Override
        public void mouseExited(MouseEvent e) {
            Component component = e.getComponent();
            if (component instanceof AbstractButton) {
@@ -112,6 +114,7 @@
            addActionListener(this);
        }
        @Override
        public void actionPerformed(ActionEvent e) {
            int i = pane.indexOfTabComponent(ClosableTabComponent.this);
            Component c = pane.getComponentAt(i);
@@ -123,6 +126,7 @@
            }
        }
        @Override
        public void updateUI() {
        }
src/main/java/com/gitblit/client/DateCellRenderer.java
@@ -26,22 +26,23 @@
/**
 * Time ago cell renderer with real date tooltip.
 *
 *
 * @author James Moger
 *
 *
 */
public class DateCellRenderer extends DefaultTableCellRenderer {
    private static final long serialVersionUID = 1L;
    private final String pattern;
    public DateCellRenderer(String pattern, Color foreground) {
        this.pattern = (pattern == null ? "yyyy-MM-dd HH:mm" : pattern);
        setForeground(foreground);
        setHorizontalAlignment(SwingConstants.CENTER);
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
src/main/java/com/gitblit/client/EditRegistrationDialog.java
@@ -42,9 +42,9 @@
/**
 * Dialog to create or edit a Gitblit registration.
 *
 *
 * @author James Moger
 *
 *
 */
public class EditRegistrationDialog extends JDialog {
@@ -71,6 +71,7 @@
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JRootPane rootPane = new JRootPane();
        rootPane.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setVisible(false);
            }
@@ -98,6 +99,7 @@
        JButton cancel = new JButton(Translation.get("gb.cancel"));
        cancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                setVisible(false);
            }
@@ -105,6 +107,7 @@
        final JButton save = new JButton(Translation.get(isLogin ? "gb.login" : "gb.save"));
        save.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (validateFields()) {
                    canceled = false;
@@ -115,6 +118,7 @@
        // on enter in password field, save or login
        passwordField.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                save.doClick();
            }
src/main/java/com/gitblit/client/EditRepositoryDialog.java
@@ -69,7 +69,7 @@
/**
 * Dialog to create/edit a repository.
 *
 *
 * @author James Moger
 */
public class EditRepositoryDialog extends JDialog {
@@ -93,7 +93,7 @@
    private JCheckBox useDocs;
    private JCheckBox useIncrementalPushTags;
    private JCheckBox showRemoteBranches;
    private JCheckBox showReadme;
@@ -107,11 +107,11 @@
    private JTextField mailingListsField;
    private JComboBox accessRestriction;
    private JRadioButton allowAuthenticated;
    private JRadioButton allowNamed;
    private JCheckBox allowForks;
    private JCheckBox verifyCommitter;
@@ -121,19 +121,19 @@
    private JPalette<String> ownersPalette;
    private JComboBox headRefField;
    private JComboBox gcPeriod;
    private JTextField gcThreshold;
    private JComboBox maxActivityCommits;
    private RegistrantPermissionsPanel usersPalette;
    private JPalette<String> setsPalette;
    private RegistrantPermissionsPanel teamsPalette;
    private JPalette<String> indexedBranchesPalette;
    private JPalette<String> preReceivePalette;
@@ -145,9 +145,9 @@
    private JLabel postReceiveInherited;
    private Set<String> repositoryNames;
    private JPanel customFieldsPanel;
    private List<JTextField> customTextfields;
    public EditRepositoryDialog(int protocolVersion) {
@@ -175,6 +175,7 @@
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JRootPane rootPane = new JRootPane();
        rootPane.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setVisible(false);
            }
@@ -194,17 +195,17 @@
        if (ArrayUtils.isEmpty(anRepository.availableRefs)) {
            headRefField = new JComboBox();
            headRefField.setEnabled(false);
            headRefField.setEnabled(false);
        } else {
            headRefField = new JComboBox(
                    anRepository.availableRefs.toArray());
            headRefField.setSelectedItem(anRepository.HEAD);
        }
        Integer []  gcPeriods =  { 1, 2, 3, 4, 5, 7, 10, 14 };
        gcPeriod = new JComboBox(gcPeriods);
        gcPeriod.setSelectedItem(anRepository.gcPeriod);
        gcThreshold = new JTextField(8);
        gcThreshold.setText(anRepository.gcThreshold);
@@ -250,21 +251,21 @@
                }
            }
        });
        boolean authenticated = anRepository.authorizationControl != null
        boolean authenticated = anRepository.authorizationControl != null
                && AuthorizationControl.AUTHENTICATED.equals(anRepository.authorizationControl);
        allowAuthenticated = new JRadioButton(Translation.get("gb.allowAuthenticatedDescription"));
        allowAuthenticated.setSelected(authenticated);
        allowAuthenticated.addItemListener(new ItemListener() {
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                if (e.getStateChange() == ItemEvent.SELECTED) {
                    usersPalette.setEnabled(false);
                    teamsPalette.setEnabled(false);
                }
            }
        });
        allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
        allowNamed.setSelected(!authenticated);
        allowNamed.addItemListener(new ItemListener() {
@@ -276,15 +277,15 @@
                }
            }
        });
        ButtonGroup group = new ButtonGroup();
        group.add(allowAuthenticated);
        group.add(allowNamed);
        JPanel authorizationPanel = new JPanel(new GridLayout(0, 1));
        authorizationPanel.add(allowAuthenticated);
        authorizationPanel.add(allowNamed);
        allowForks = new JCheckBox(Translation.get("gb.allowForksDescription"), anRepository.allowForks);
        verifyCommitter = new JCheckBox(Translation.get("gb.verifyCommitterDescription"), anRepository.verifyCommitter);
@@ -387,7 +388,7 @@
        JPanel postReceivePanel = new JPanel(new BorderLayout(5, 5));
        postReceivePanel.add(postReceivePalette, BorderLayout.CENTER);
        postReceivePanel.add(postReceiveInherited, BorderLayout.WEST);
        customFieldsPanel = new JPanel();
        customFieldsPanel.setLayout(new BoxLayout(customFieldsPanel, BoxLayout.Y_AXIS));
        JScrollPane customFieldsScrollPane = new JScrollPane(customFieldsPanel);
@@ -406,14 +407,15 @@
        }
        panel.addTab(Translation.get("gb.preReceiveScripts"), preReceivePanel);
        panel.addTab(Translation.get("gb.postReceiveScripts"), postReceivePanel);
        panel.addTab(Translation.get("gb.customFields"), customFieldsScrollPane);
        setupAccessPermissions(anRepository.accessRestriction);
        JButton createButton = new JButton(Translation.get("gb.save"));
        createButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (validateFields()) {
                    canceled = false;
@@ -424,6 +426,7 @@
        JButton cancelButton = new JButton(Translation.get("gb.cancel"));
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                canceled = true;
                setVisible(false);
@@ -452,7 +455,7 @@
        pack();
        nameField.requestFocus();
    }
    private JPanel newFieldPanel(String label, JComponent comp) {
        return newFieldPanel(label, 150, comp);
    }
@@ -466,12 +469,12 @@
        panel.add(comp);
        return panel;
    }
    private void setupAccessPermissions(AccessRestrictionType art) {
        if (AccessRestrictionType.NONE.equals(art)) {
            usersPalette.setEnabled(false);
            teamsPalette.setEnabled(false);
            allowAuthenticated.setEnabled(false);
            allowNamed.setEnabled(false);
            verifyCommitter.setEnabled(false);
@@ -479,7 +482,7 @@
            allowAuthenticated.setEnabled(true);
            allowNamed.setEnabled(true);
            verifyCommitter.setEnabled(true);
            if (allowNamed.isSelected()) {
                usersPalette.setEnabled(true);
                teamsPalette.setEnabled(true);
@@ -575,7 +578,7 @@
        repository.skipSizeCalculation = skipSizeCalculation.isSelected();
        repository.skipSummaryMetrics = skipSummaryMetrics.isSelected();
        repository.maxActivityCommits = (Integer) maxActivityCommits.getSelectedItem();
        repository.isFrozen = isFrozen.isSelected();
        repository.allowForks = allowForks.isSelected();
        repository.verifyCommitter = verifyCommitter.isSelected();
@@ -594,7 +597,7 @@
        repository.accessRestriction = (AccessRestrictionType) accessRestriction
                .getSelectedItem();
        repository.authorizationControl = allowAuthenticated.isSelected() ?
        repository.authorizationControl = allowAuthenticated.isSelected() ?
                AuthorizationControl.AUTHENTICATED : AuthorizationControl.NAMED;
        repository.federationStrategy = (FederationStrategy) federationStrategy
                .getSelectedItem();
@@ -602,11 +605,11 @@
        if (repository.federationStrategy.exceeds(FederationStrategy.EXCLUDE)) {
            repository.federationSets = setsPalette.getSelections();
        }
        repository.indexedBranches = indexedBranchesPalette.getSelections();
        repository.preReceiveScripts = preReceivePalette.getSelections();
        repository.postReceiveScripts = postReceivePalette.getSelections();
        // Custom Fields
        repository.customFields = new LinkedHashMap<String, String>();
        if (customTextfields != null) {
@@ -623,7 +626,7 @@
        JOptionPane.showMessageDialog(EditRepositoryDialog.this, message,
                Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
    }
    public void setAccessRestriction(AccessRestrictionType restriction) {
        this.accessRestriction.setSelectedItem(restriction);
        setupAccessPermissions(restriction);
@@ -658,7 +661,7 @@
    public void setFederationSets(List<String> all, List<String> selected) {
        setsPalette.setObjects(all, selected);
    }
    public void setIndexedBranches(List<String> all, List<String> selected) {
        indexedBranchesPalette.setObjects(all, selected);
    }
@@ -701,11 +704,11 @@
    public List<RegistrantAccessPermission> getTeamAccessPermissions() {
        return teamsPalette.getPermissions();
    }
    public void setCustomFields(RepositoryModel repository, Map<String, String> customFields) {
        customFieldsPanel.removeAll();
        customTextfields = new ArrayList<JTextField>();
        final Insets insets = new Insets(5, 5, 5, 5);
        JPanel fields = new JPanel(new GridLayout(0, 1, 0, 5)) {
@@ -715,8 +718,8 @@
            public Insets getInsets() {
                return insets;
            }
        };
        };
        for (Map.Entry<String, String> entry : customFields.entrySet()) {
            String field = entry.getKey();
            String value = "";
@@ -725,14 +728,14 @@
            }
            JTextField textField = new JTextField(value);
            textField.setName(field);
            textField.setPreferredSize(new Dimension(450, 26));
            fields.add(newFieldPanel(entry.getValue(), 250, textField));
            customTextfields.add(textField);
        }
        JScrollPane jsp = new JScrollPane(fields);
        JScrollPane jsp = new JScrollPane(fields);
        jsp.getVerticalScrollBar().setBlockIncrement(100);
        jsp.getVerticalScrollBar().setUnitIncrement(100);
        jsp.setViewportBorder(null);
@@ -743,7 +746,7 @@
    /**
     * ListCellRenderer to display descriptive text about the access
     * restriction.
     *
     *
     */
    private class AccessRestrictionRenderer extends DefaultListCellRenderer {
@@ -753,7 +756,7 @@
        public Component getListCellRendererComponent(JList list, Object value,
                int index, boolean isSelected, boolean cellHasFocus) {
            super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            if (value instanceof AccessRestrictionType) {
                AccessRestrictionType restriction = (AccessRestrictionType) value;
                switch (restriction) {
src/main/java/com/gitblit/client/EditTeamDialog.java
@@ -70,11 +70,11 @@
    private boolean canceled = true;
    private JTextField teamnameField;
    private JCheckBox canAdminCheckbox;
    private JCheckBox canForkCheckbox;
    private JCheckBox canCreateCheckbox;
    private JTextField mailingListsField;
@@ -117,6 +117,7 @@
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JRootPane rootPane = new JRootPane();
        rootPane.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setVisible(false);
            }
@@ -127,7 +128,7 @@
    private void initialize(int protocolVersion, TeamModel aTeam) {
        teamnameField = new JTextField(aTeam.name == null ? "" : aTeam.name, 25);
        canAdminCheckbox = new JCheckBox(Translation.get("gb.canAdminDescription"), aTeam.canAdmin);
        canAdminCheckbox = new JCheckBox(Translation.get("gb.canAdminDescription"), aTeam.canAdmin);
        canForkCheckbox = new JCheckBox(Translation.get("gb.canForkDescription"), aTeam.canFork);
        canCreateCheckbox = new JCheckBox(Translation.get("gb.canCreateDescription"), aTeam.canCreate);
@@ -146,7 +147,7 @@
        repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
        userPalette = new JPalette<String>();
        userPalette.setEnabled(settings.supportsTeamMembershipChanges);
        JPanel fieldsPanelTop = new JPanel(new BorderLayout());
        fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH);
@@ -154,6 +155,7 @@
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return _insets;
            }
@@ -164,6 +166,7 @@
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return _insets;
            }
@@ -175,7 +178,7 @@
        JPanel preReceivePanel = new JPanel(new BorderLayout(5, 5));
        preReceivePanel.add(preReceivePalette, BorderLayout.CENTER);
        preReceivePanel.add(preReceiveInherited, BorderLayout.WEST);
        postReceivePalette = new JPalette<String>(true);
        postReceiveInherited = new JLabel();
        JPanel postReceivePanel = new JPanel(new BorderLayout(5, 5));
@@ -191,6 +194,7 @@
        JButton createButton = new JButton(Translation.get("gb.save"));
        createButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (validateFields()) {
                    canceled = false;
@@ -201,6 +205,7 @@
        JButton cancelButton = new JButton(Translation.get("gb.cancel"));
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                canceled = true;
                setVisible(false);
@@ -317,10 +322,10 @@
            if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
                    && repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
                restricted.add(repo.name);
            }
            }
        }
        StringUtils.sortRepositorynames(restricted);
        List<String> list = new ArrayList<String>();
        // repositories
        list.add(".*");
@@ -339,7 +344,7 @@
            // all repositories excluding personal repositories
            list.add("[^" + prefix + "].*");
        }
        String lastProject = null;
        for (String repo : restricted) {
            String projectPath = StringUtils.getFirstPathElement(repo);
src/main/java/com/gitblit/client/EditUserDialog.java
@@ -79,29 +79,29 @@
    private JPasswordField passwordField;
    private JPasswordField confirmPasswordField;
    private JTextField displayNameField;
    private JTextField emailAddressField;
    private JCheckBox canAdminCheckbox;
    private JCheckBox canForkCheckbox;
    private JCheckBox canCreateCheckbox;
    private JCheckBox notFederatedCheckbox;
    private JTextField organizationalUnitField;
    private JTextField organizationField;
    private JTextField localityField;
    private JTextField stateProvinceField;
    private JTextField countryCodeField;
    private RegistrantPermissionsPanel repositoryPalette;
    private JPalette<TeamModel> teamsPalette;
@@ -132,6 +132,7 @@
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JRootPane rootPane = new JRootPane();
        rootPane.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setVisible(false);
            }
@@ -146,19 +147,19 @@
                25);
        displayNameField = new JTextField(anUser.displayName == null ? "" : anUser.displayName, 25);
        emailAddressField = new JTextField(anUser.emailAddress == null ? "" : anUser.emailAddress, 25);
        canAdminCheckbox = new JCheckBox(Translation.get("gb.canAdminDescription"), anUser.canAdmin);
        canAdminCheckbox = new JCheckBox(Translation.get("gb.canAdminDescription"), anUser.canAdmin);
        canForkCheckbox = new JCheckBox(Translation.get("gb.canForkDescription"), anUser.canFork);
        canCreateCheckbox = new JCheckBox(Translation.get("gb.canCreateDescription"), anUser.canCreate);
        notFederatedCheckbox = new JCheckBox(
                Translation.get("gb.excludeFromFederationDescription"),
                anUser.excludeFromFederation);
        organizationalUnitField = new JTextField(anUser.organizationalUnit == null ? "" : anUser.organizationalUnit, 25);
        organizationField = new JTextField(anUser.organization == null ? "" : anUser.organization, 25);
        localityField = new JTextField(anUser.locality == null ? "" : anUser.locality, 25);
        stateProvinceField = new JTextField(anUser.stateProvince == null ? "" : anUser.stateProvince, 25);
        countryCodeField = new JTextField(anUser.countryCode == null ? "" : anUser.countryCode, 15);
        // credentials are optionally controlled by 3rd-party authentication
        usernameField.setEnabled(settings.supportsCredentialChanges);
        passwordField.setEnabled(settings.supportsCredentialChanges);
@@ -166,7 +167,7 @@
        displayNameField.setEnabled(settings.supportsDisplayNameChanges);
        emailAddressField.setEnabled(settings.supportsEmailAddressChanges);
        organizationalUnitField.setEnabled(settings.supportsDisplayNameChanges);
        organizationField.setEnabled(settings.supportsDisplayNameChanges);
        localityField.setEnabled(settings.supportsDisplayNameChanges);
@@ -191,7 +192,7 @@
        attributesPanel.add(newFieldPanel(Translation.get("gb.locality") + " (L)", localityField));
        attributesPanel.add(newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvinceField));
        attributesPanel.add(newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCodeField));
        final Insets _insets = new Insets(5, 5, 5, 5);
        repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
        teamsPalette = new JPalette<TeamModel>();
@@ -207,6 +208,7 @@
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return _insets;
            }
@@ -217,6 +219,7 @@
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return _insets;
            }
@@ -233,6 +236,7 @@
        JButton createButton = new JButton(Translation.get("gb.save"));
        createButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                if (validateFields()) {
                    canceled = false;
@@ -243,6 +247,7 @@
        JButton cancelButton = new JButton(Translation.get("gb.cancel"));
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                canceled = true;
                setVisible(false);
@@ -349,7 +354,7 @@
            // no change in password
            user.password = password;
        }
        user.displayName = displayNameField.getText().trim();
        user.emailAddress = emailAddressField.getText().trim();
@@ -363,7 +368,7 @@
        user.locality = localityField.getText().trim();
        user.stateProvince = stateProvinceField.getText().trim();
        user.countryCode = countryCodeField.getText().trim();
        for (RegistrantAccessPermission rp : repositoryPalette.getPermissions()) {
            user.setRepositoryPermission(rp.registrant, rp.permission);
        }
@@ -394,16 +399,16 @@
                if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
                        && repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
                    restricted.add(repo.name);
                }
                }
            }
            repoMap.put(repo.name.toLowerCase(), repo);
        }
        StringUtils.sortRepositorynames(restricted);
        List<String> list = new ArrayList<String>();
        // repositories
        list.add(".*");
        String prefix;
        if (settings.hasKey(Keys.git.userRepositoryPrefix)) {
            prefix = settings.get(Keys.git.userRepositoryPrefix).currentValue;
@@ -418,7 +423,7 @@
            // all repositories excluding personal repositories
            list.add("[^" + prefix + "].*");
        }
        String lastProject = null;
        for (String repo : restricted) {
            String projectPath = StringUtils.getFirstPathElement(repo).toLowerCase();
@@ -440,7 +445,7 @@
                list.remove(rp.registrant.toLowerCase());
            }
        }
        // update owner and missing permissions for editing
        for (RegistrantAccessPermission permission : permissions) {
            if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
@@ -471,7 +476,7 @@
        }
        teamsPalette.setObjects(teams, selected);
    }
    public UserModel getUser() {
        if (canceled) {
            return null;
src/main/java/com/gitblit/client/FeedEntryTableModel.java
@@ -26,9 +26,9 @@
/**
 * Table model for a list of retrieved feed entries.
 *
 *
 * @author James Moger
 *
 *
 */
public class FeedEntryTableModel extends AbstractTableModel {
@@ -84,11 +84,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (Columns.Date.ordinal() == columnIndex) {
            return Date.class;
src/main/java/com/gitblit/client/FeedsPanel.java
@@ -49,9 +49,9 @@
/**
 * RSS Feeds Panel displays recent entries and launches the browser to view the
 * commit. commitdiff, or tree of a commit.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class FeedsPanel extends JPanel {
@@ -95,6 +95,7 @@
        prev.setToolTipText(Translation.get("gb.pagePrevious"));
        prev.setEnabled(false);
        prev.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshFeeds(--page);
            }
@@ -104,6 +105,7 @@
        next.setToolTipText(Translation.get("gb.pageNext"));
        next.setEnabled(false);
        next.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshFeeds(++page);
            }
@@ -111,6 +113,7 @@
        JButton refreshFeeds = new JButton(Translation.get("gb.refresh"));
        refreshFeeds.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshFeeds(0);
            }
@@ -119,6 +122,7 @@
        final JButton viewCommit = new JButton(Translation.get("gb.view"));
        viewCommit.setEnabled(false);
        viewCommit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                viewCommit();
            }
@@ -127,6 +131,7 @@
        final JButton viewCommitDiff = new JButton(Translation.get("gb.commitdiff"));
        viewCommitDiff.setEnabled(false);
        viewCommitDiff.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                viewCommitDiff();
            }
@@ -135,6 +140,7 @@
        final JButton viewTree = new JButton(Translation.get("gb.tree"));
        viewTree.setEnabled(false);
        viewTree.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                viewTree();
            }
@@ -142,6 +148,7 @@
        JButton subscribeFeeds = new JButton(Translation.get("gb.subscribe") + "...");
        subscribeFeeds.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                subscribeFeeds(gitblit.getAvailableFeeds());
            }
@@ -171,6 +178,7 @@
        table.getColumn(name).setCellRenderer(new MessageRenderer(gitblit));
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    if (e.isControlDown()) {
@@ -200,6 +208,7 @@
        repositorySelector.setRenderer(nameRenderer);
        repositorySelector.setForeground(nameRenderer.getForeground());
        repositorySelector.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                // repopulate the author list based on repository selection
                // preserve author selection, if possible
@@ -221,6 +230,7 @@
        authorSelector.setRenderer(nameRenderer);
        authorSelector.setForeground(nameRenderer.getForeground());
        authorSelector.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                filterFeeds();
            }
@@ -371,6 +381,7 @@
        if (repository.equals(ALL)) {
            // author filter
            containsFilter = new RowFilter<FeedEntryTableModel, Object>() {
                @Override
                public boolean include(
                        Entry<? extends FeedEntryTableModel, ? extends Object> entry) {
                    return entry.getStringValue(authorIndex).equalsIgnoreCase(author);
@@ -379,6 +390,7 @@
        } else if (author.equals(ALL)) {
            // repository filter
            containsFilter = new RowFilter<FeedEntryTableModel, Object>() {
                @Override
                public boolean include(
                        Entry<? extends FeedEntryTableModel, ? extends Object> entry) {
                    return entry.getStringValue(repositoryIndex).equalsIgnoreCase(repository);
@@ -387,6 +399,7 @@
        } else {
            // repository-author filter
            containsFilter = new RowFilter<FeedEntryTableModel, Object>() {
                @Override
                public boolean include(
                        Entry<? extends FeedEntryTableModel, ? extends Object> entry) {
                    boolean authorMatch = entry.getStringValue(authorIndex)
src/main/java/com/gitblit/client/FeedsTableModel.java
@@ -25,9 +25,9 @@
/**
 * Table model of a list of available feeds.
 *
 *
 * @author James Moger
 *
 *
 */
public class FeedsTableModel extends AbstractTableModel {
@@ -77,11 +77,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        Columns col = Columns.values()[columnIndex];
        switch (col) {
src/main/java/com/gitblit/client/GitblitClient.java
@@ -55,9 +55,9 @@
/**
 * GitblitClient is a object that retrieves data from a Gitblit server, caches
 * it for local operations, and allows updating or creating Gitblit objects.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitblitClient implements Serializable {
@@ -191,7 +191,7 @@
            return sb.toString();
        }
    }
    public AccessRestrictionType getDefaultAccessRestriction() {
        String restriction = "PUSH";
        if (settings.hasKey(Keys.git.defaultAccessRestriction)) {
@@ -211,7 +211,7 @@
    /**
     * Returns the list of pre-receive scripts the repository inherited from the
     * global settings and team affiliations.
     *
     *
     * @param repository
     *            if null only the globally specified scripts are returned
     * @return a list of scripts
@@ -243,7 +243,7 @@
     * Returns the list of all available Groovy pre-receive push hook scripts
     * that are not already inherited by the repository. Script files must have
     * .groovy extension
     *
     *
     * @param repository
     *            optional parameter
     * @return list of available hook scripts
@@ -264,7 +264,7 @@
    /**
     * Returns the list of post-receive scripts the repository inherited from
     * the global settings and team affiliations.
     *
     *
     * @param repository
     *            if null only the globally specified scripts are returned
     * @return a list of scripts
@@ -295,7 +295,7 @@
     * Returns the list of unused Groovy post-receive push hook scripts that are
     * not already inherited by the repository. Script files must have .groovy
     * extension
     *
     *
     * @param repository
     *            optional parameter
     * @return list of available hook scripts
@@ -305,7 +305,7 @@
        // create list of available scripts by excluding inherited scripts
        List<String> scripts = new ArrayList<String>();
        if (!ArrayUtils.isEmpty(settings.pushScripts)) {
        if (!ArrayUtils.isEmpty(settings.pushScripts)) {
            for (String script : settings.pushScripts) {
                if (!inherited.contains(script)) {
                    scripts.add(script);
@@ -478,7 +478,7 @@
    public List<UserModel> getUsers() {
        return allUsers;
    }
    public UserModel getUser(String username) {
        for (UserModel user : getUsers()) {
            if (user.username.equalsIgnoreCase(username)) {
@@ -506,11 +506,11 @@
        }
        return usernames;
    }
    /**
     * Returns the effective list of permissions for this user, taking into account
     * team memberships, ownerships.
     *
     *
     * @param user
     * @return the effective list of permissions for the user
     */
@@ -541,12 +541,12 @@
                set.add(rp);
            }
        }
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
        Collections.sort(list);
        return list;
    }
    public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
@@ -593,7 +593,7 @@
        }
        return teamnames;
    }
    public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
        List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
        for (TeamModel team : allTeams) {
@@ -626,7 +626,7 @@
    public List<RepositoryModel> getRepositories() {
        return allRepositories;
    }
    public RepositoryModel getRepository(String name) {
        for (RepositoryModel repository : allRepositories) {
            if (repository.name.equalsIgnoreCase(name)) {
@@ -682,7 +682,7 @@
    public boolean deleteRepository(RepositoryModel repository) throws IOException {
        return RpcUtils.deleteRepository(repository, url, account, password);
    }
    public boolean clearRepositoryCache() throws IOException {
        return RpcUtils.clearRepositoryCache(url, account, password);
    }
src/main/java/com/gitblit/client/GitblitManager.java
@@ -70,9 +70,9 @@
/**
 * Gitblit Manager issues JSON RPC requests to a Gitblit server.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitblitManager extends JFrame implements RegistrationsDialog.RegistrationListener {
@@ -173,6 +173,7 @@
        JMenuItem manage = new JMenuItem(Translation.get("gb.manage") + "...");
        manage.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_DOWN_MASK, false));
        manage.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                manageRegistrations();
            }
@@ -287,6 +288,7 @@
            item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + i, KeyEvent.CTRL_DOWN_MASK,
                    false));
            item.addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    login(reg);
                }
@@ -317,6 +319,7 @@
                GitblitRegistration reg = new GitblitRegistration(server, url, account, password) {
                    private static final long serialVersionUID = 1L;
                    @Override
                    protected void cacheFeeds() {
                        writeFeedCache(this);
                    }
@@ -444,6 +447,7 @@
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
src/main/java/com/gitblit/client/GitblitManagerLauncher.java
@@ -36,9 +36,9 @@
/**
 * Downloads dependencies and launches Gitblit Manager.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitblitManagerLauncher {
@@ -51,10 +51,10 @@
    public static void main(String[] args) {
        final SplashScreen splash = SplashScreen.getSplashScreen();
        File libFolder = new File("ext");
        List<File> jars = findJars(libFolder.getAbsoluteFile());
        // sort the jars by name and then reverse the order so the newer version
        // of the library gets loaded in the event that this is an upgrade
        Collections.sort(jars);
@@ -67,7 +67,7 @@
            }
        }
        updateSplash(splash, Translation.get("gb.starting") + " Gitblit Manager...");
        GitblitManager.main(args);
    }
@@ -78,12 +78,13 @@
        }
        try {
            EventQueue.invokeAndWait(new Runnable() {
                @Override
                public void run() {
                    Graphics2D g = splash.createGraphics();
                    if (g != null) {
                        // Splash is 320x120
                        FontMetrics fm = g.getFontMetrics();
                        // paint startup status
                        g.setColor(Color.darkGray);
                        int h = fm.getHeight() + fm.getMaxDescent();
@@ -96,7 +97,7 @@
                        g.setColor(Color.WHITE);
                        int xw = fm.stringWidth(string);
                        g.drawString(string, x + ((w - xw) / 2), y - 5);
                        // paint version
                        String ver = "v" + Constants.getVersion();
                        int vw = g.getFontMetrics().stringWidth(ver);
@@ -110,7 +111,7 @@
            t.printStackTrace();
        }
    }
    public static List<File> findJars(File folder) {
        List<File> jars = new ArrayList<File>();
        if (folder.exists()) {
@@ -135,7 +136,7 @@
    /**
     * Adds a file to the classpath
     *
     *
     * @param f
     *            the file to be added
     * @throws IOException
src/main/java/com/gitblit/client/GitblitPanel.java
@@ -31,9 +31,9 @@
/**
 * GitblitPanel is a container for the repository, users, settings, etc panels.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitblitPanel extends JPanel implements CloseTabListener {
@@ -50,7 +50,7 @@
    private FeedsPanel feedsPanel;
    private UsersPanel usersPanel;
    private TeamsPanel teamsPanel;
    private SettingsPanel settingsPanel;
@@ -69,6 +69,7 @@
        tabs.addTab(Translation.get("gb.settings"), createSettingsPanel());
        tabs.addTab(Translation.get("gb.status"), createStatusPanel());
        tabs.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                tabs.getSelectedComponent().requestFocus();
            }
@@ -92,7 +93,7 @@
            protected void updateUsersTable() {
                usersPanel.updateTable(false);
            }
            @Override
            protected void updateTeamsTable() {
                teamsPanel.updateTable(false);
@@ -116,9 +117,9 @@
    private JPanel createUsersPanel() {
        usersPanel = new UsersPanel(gitblit) {
            private static final long serialVersionUID = 1L;
            @Override
            protected void updateTeamsTable() {
                teamsPanel.updateTable(false);
@@ -126,10 +127,10 @@
        };
        return usersPanel;
    }
    private JPanel createTeamsPanel() {
        teamsPanel = new TeamsPanel(gitblit) {
            private static final long serialVersionUID = 1L;
            @Override
@@ -138,7 +139,7 @@
            }
        };
        return teamsPanel;
    }
    }
    private JPanel createSettingsPanel() {
        settingsPanel = new SettingsPanel(gitblit);
src/main/java/com/gitblit/client/GitblitRegistration.java
@@ -25,9 +25,9 @@
/**
 * Simple class to encapsulate a Gitblit server registration.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitblitRegistration implements Serializable, Comparable<GitblitRegistration> {
src/main/java/com/gitblit/client/GitblitWorker.java
@@ -50,6 +50,7 @@
        return doRequest();
    }
    @Override
    protected void done() {
        parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
        try {
src/main/java/com/gitblit/client/IndicatorsRenderer.java
@@ -30,9 +30,9 @@
/**
 * Renders the type indicators (tickets, frozen, access restriction, etc) in a
 * single cell.
 *
 *
 * @author James Moger
 *
 *
 */
public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Serializable {
@@ -53,9 +53,9 @@
    private final ImageIcon frozenIcon;
    private final ImageIcon federatedIcon;
    private final ImageIcon forkIcon;
    private final ImageIcon sparkleshareIcon;
    public IndicatorsRenderer() {
src/main/java/com/gitblit/client/JPalette.java
@@ -58,6 +58,7 @@
        add = new JButton("->");
        add.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                List<T> move = new ArrayList<T>();
                if (available.getSelectedRowCount() <= 0) {
@@ -65,7 +66,7 @@
                }
                for (int row : available.getSelectedRows()) {
                    int modelIndex = available.convertRowIndexToModel(row);
                    T item = (T) availableModel.list.get(modelIndex);
                    T item = availableModel.list.get(modelIndex);
                    move.add(item);
                }
                availableModel.list.removeAll(move);
@@ -76,6 +77,7 @@
        });
        subtract = new JButton("<-");
        subtract.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                List<T> move = new ArrayList<T>();
                if (selected.getSelectedRowCount() <= 0) {
@@ -83,7 +85,7 @@
                }
                for (int row : selected.getSelectedRows()) {
                    int modelIndex = selected.convertRowIndexToModel(row);
                    T item = (T) selectedModel.list.get(modelIndex);
                    T item = selectedModel.list.get(modelIndex);
                    move.add(item);
                }
                selectedModel.list.removeAll(move);
@@ -96,6 +98,7 @@
        up = new JButton("\u2191");
        up.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                int row = selected.getSelectedRow();
                if (row > 0) {
@@ -108,6 +111,7 @@
        down = new JButton("\u2193");
        down.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                int row = selected.getSelectedRow();
                if (row < selected.getRowCount() - 1) {
@@ -152,7 +156,7 @@
        panel.add(jsp, BorderLayout.CENTER);
        return panel;
    }
    @Override
    public void setEnabled(boolean enabled) {
        super.setEnabled(enabled);
@@ -211,6 +215,7 @@
            return Translation.get("gb.name");
        }
        @Override
        public Class<?> getColumnClass(int columnIndex) {
            return String.class;
        }
src/main/java/com/gitblit/client/MessageRenderer.java
@@ -36,20 +36,20 @@
/**
 * Message renderer displays the short log message and then any refs in a style
 * like the site.
 *
 *
 * @author James Moger
 *
 *
 */
public class MessageRenderer extends JPanel implements TableCellRenderer, Serializable {
    private static final long serialVersionUID = 1L;
    private final GitblitClient gitblit;
    private final ImageIcon mergeIcon;
    private final ImageIcon blankIcon;
    private final JLabel messageLabel;
    private final JLabel headLabel;
@@ -67,12 +67,12 @@
    public MessageRenderer(GitblitClient gitblit) {
        super(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, 1));
        this.gitblit = gitblit;
        mergeIcon = new ImageIcon(getClass().getResource("/commit_merge_16x16.png"));
        blankIcon = new ImageIcon(getClass().getResource("/blank.png"));
        messageLabel = new JLabel();
        headLabel = newRefLabel();
        branchLabel = newRefLabel();
        remoteLabel = newRefLabel();
@@ -85,7 +85,7 @@
        add(tagLabel);
    }
    private JLabel newRefLabel() {
    private JLabel newRefLabel() {
        JLabel label = new JLabel();
        label.setOpaque(true);
        Font font = label.getFont();
@@ -131,6 +131,7 @@
        label.setVisible(true);
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        if (isSelected)
src/main/java/com/gitblit/client/NameRenderer.java
@@ -26,9 +26,9 @@
/**
 * Repository name cell renderer. This renderer shows the group name in a gray
 * color and accentuates the repository name in a cornflower blue color.
 *
 *
 * @author James Moger
 *
 *
 */
public class NameRenderer extends DefaultTableCellRenderer implements ListCellRenderer {
@@ -56,6 +56,7 @@
        return sb.toString();
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
src/main/java/com/gitblit/client/PropertiesTableModel.java
@@ -25,9 +25,9 @@
/**
 * Table model of a map of properties.
 *
 *
 * @author James Moger
 *
 *
 */
public class PropertiesTableModel extends AbstractTableModel {
@@ -82,11 +82,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }
src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -46,7 +46,7 @@
public class RegistrantPermissionsPanel extends JPanel {
    private static final long serialVersionUID = 1L;
    private JTable permissionsTable;
    private RegistrantPermissionsTableModel tableModel;
@@ -67,7 +67,7 @@
        permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT, new RowRenderer() {
            Color clear = new Color(0, 0, 0, 0);
            Color iceGray = new Color(0xf0, 0xf0, 0xf0);
            @Override
            public void prepareRow(Component c, boolean isSelected, int row, int column) {
                if (isSelected) {
@@ -85,19 +85,20 @@
        permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150));
        JScrollPane jsp = new JScrollPane(permissionsTable);
        add(jsp, BorderLayout.CENTER);
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Registrant.ordinal())
        .setCellRenderer(new NameRenderer());
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Type.ordinal())
                .setCellRenderer(new PermissionTypeRenderer());
        permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Permission.ordinal())
        .setCellEditor(new AccessPermissionEditor());
        registrantModel = new DefaultComboBoxModel();
        registrantSelector = new JComboBox(registrantModel);
        permissionSelector = new JComboBox(AccessPermission.NEWPERMISSIONS);
        addButton = new JButton(Translation.get("gb.add"));
        addButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (registrantSelector.getSelectedIndex() < 0) {
                    return;
@@ -105,7 +106,7 @@
                if (permissionSelector.getSelectedIndex() < 0) {
                    return;
                }
                RegistrantAccessPermission rp = new RegistrantAccessPermission(registrantType);
                rp.registrant = registrantSelector.getSelectedItem().toString();
                rp.permission = (AccessPermission) permissionSelector.getSelectedItem();
@@ -119,16 +120,16 @@
                tableModel.permissions.add(rp);
                // resort permissions after insert to convey idea of eval order
                Collections.sort(tableModel.permissions);
                registrantModel.removeElement(rp.registrant);
                registrantSelector.setSelectedIndex(-1);
                registrantSelector.invalidate();
                addPanel.setVisible(registrantModel.getSize() > 0);
                tableModel.fireTableDataChanged();
            }
        });
        addPanel = new JPanel();
        addPanel.add(registrantSelector);
        addPanel.add(permissionSelector);
@@ -172,7 +173,7 @@
            registrantModel.addElement(registrant);
        }
        tableModel.setPermissions(permissions);
        registrantSelector.setSelectedIndex(-1);
        permissionSelector.setSelectedIndex(-1);
        addPanel.setVisible(filtered.size() > 0);
@@ -181,16 +182,16 @@
    public List<RegistrantAccessPermission> getPermissions() {
        return tableModel.permissions;
    }
    private class AccessPermissionEditor extends DefaultCellEditor {
        private static final long serialVersionUID = 1L;
        public AccessPermissionEditor() {
            super(new JComboBox(AccessPermission.values()));
            super(new JComboBox(AccessPermission.values()));
        }
    }
    private class PermissionTypeRenderer extends DefaultTableCellRenderer {
        private static final long serialVersionUID = 1L;
src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java
@@ -25,9 +25,9 @@
/**
 * Table model of a registrant permissions.
 *
 *
 * @author James Moger
 *
 *
 */
public class RegistrantPermissionsTableModel extends AbstractTableModel {
@@ -82,11 +82,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (columnIndex == Columns.Permission.ordinal()) {
            return AccessPermission.class;
@@ -95,7 +96,7 @@
        }
        return String.class;
    }
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        if (columnIndex == Columns.Permission.ordinal()) {
@@ -123,7 +124,7 @@
        }
        return null;
    }
    @Override
    public void setValueAt(Object o, int rowIndex, int columnIndex) {
        RegistrantAccessPermission rp = permissions.get(rowIndex);
src/main/java/com/gitblit/client/RegistrationsDialog.java
@@ -41,9 +41,9 @@
/**
 * Displays a list of registrations and allows management of server
 * registrations.
 *
 *
 * @author James Moger
 *
 *
 */
public class RegistrationsDialog extends JDialog {
@@ -82,6 +82,7 @@
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JRootPane rootPane = new JRootPane();
        rootPane.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setVisible(false);
            }
@@ -98,6 +99,7 @@
                .getColumnName(RegistrationsTableModel.Columns.Name.ordinal());
        registrationsTable.getColumn(id).setCellRenderer(nameRenderer);
        registrationsTable.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    login();
@@ -107,6 +109,7 @@
        final JButton create = new JButton(Translation.get("gb.create"));
        create.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                create();
            }
@@ -115,6 +118,7 @@
        final JButton login = new JButton(Translation.get("gb.login"));
        login.setEnabled(false);
        login.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                login();
            }
@@ -123,6 +127,7 @@
        final JButton edit = new JButton(Translation.get("gb.edit"));
        edit.setEnabled(false);
        edit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                edit();
            }
@@ -131,6 +136,7 @@
        final JButton delete = new JButton(Translation.get("gb.delete"));
        delete.setEnabled(false);
        delete.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                delete();
            }
@@ -162,6 +168,7 @@
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return insets;
            }
src/main/java/com/gitblit/client/RegistrationsTableModel.java
@@ -23,9 +23,9 @@
/**
 * Table model of a list of Gitblit server registrations.
 *
 *
 * @author James Moger
 *
 *
 */
public class RegistrationsTableModel extends AbstractTableModel {
@@ -73,11 +73,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (columnIndex == Columns.Last_Login.ordinal()) {
            return Date.class;
src/main/java/com/gitblit/client/RepositoriesPanel.java
@@ -57,9 +57,9 @@
/**
 * RSS Feeds Panel displays recent entries and launches the browser to view the
 * commit. commitdiff, or tree of a commit.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class RepositoriesPanel extends JPanel {
@@ -95,6 +95,7 @@
        final JButton browseRepository = new JButton(Translation.get("gb.browse"));
        browseRepository.setEnabled(false);
        browseRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                RepositoryModel model = getSelectedRepositories().get(0);
                String url = gitblit.getURL("summary", model.name, null);
@@ -104,13 +105,15 @@
        JButton refreshRepositories = new JButton(Translation.get("gb.refresh"));
        refreshRepositories.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshRepositories();
            }
        });
        clearCache = new JButton(Translation.get("gb.clearCache"));
        clearCache.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                clearCache();
            }
@@ -118,6 +121,7 @@
        createRepository = new JButton(Translation.get("gb.create"));
        createRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                createRepository();
            }
@@ -126,6 +130,7 @@
        editRepository = new JButton(Translation.get("gb.edit"));
        editRepository.setEnabled(false);
        editRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                editRepository(getSelectedRepositories().get(0));
            }
@@ -134,6 +139,7 @@
        delRepository = new JButton(Translation.get("gb.delete"));
        delRepository.setEnabled(false);
        delRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                deleteRepositories(getSelectedRepositories());
            }
@@ -142,6 +148,7 @@
        final JButton subscribeRepository = new JButton(Translation.get("gb.subscribe") + "...");
        subscribeRepository.setEnabled(false);
        subscribeRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                List<FeedModel> feeds = gitblit.getAvailableFeeds(getSelectedRepositories().get(0));
                subscribeFeeds(feeds);
@@ -151,6 +158,7 @@
        final JButton logRepository = new JButton(Translation.get("gb.log") + "...");
        logRepository.setEnabled(false);
        logRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                RepositoryModel model = getSelectedRepositories().get(0);
                showSearchDialog(false, model);
@@ -160,6 +168,7 @@
        final JButton searchRepository = new JButton(Translation.get("gb.search") + "...");
        searchRepository.setEnabled(false);
        searchRepository.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                RepositoryModel model = getSelectedRepositories().get(0);
                showSearchDialog(true, model);
@@ -223,6 +232,7 @@
        });
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2 && gitblit.allowManagement()) {
                    editRepository(getSelectedRepositories().get(0));
@@ -232,11 +242,13 @@
        filterTextfield = new JTextField();
        filterTextfield.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                filterRepositories(filterTextfield.getText());
            }
        });
        filterTextfield.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                filterRepositories(filterTextfield.getText());
            }
@@ -318,6 +330,7 @@
            return;
        }
        RowFilter<RepositoriesTableModel, Object> containsFilter = new RowFilter<RepositoriesTableModel, Object>() {
            @Override
            public boolean include(Entry<? extends RepositoriesTableModel, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
@@ -359,7 +372,7 @@
        };
        worker.execute();
    }
    protected void clearCache() {
        GitblitWorker worker = new GitblitWorker(RepositoriesPanel.this,
                RpcRequest.CLEAR_REPOSITORY_CACHE) {
@@ -383,7 +396,7 @@
    /**
     * Displays the create repository dialog and fires a SwingWorker to update
     * the server, if appropriate.
     *
     *
     */
    protected void createRepository() {
        EditRepositoryDialog dialog = new EditRepositoryDialog(gitblit.getProtocolVersion());
@@ -444,7 +457,7 @@
    /**
     * Displays the edit repository dialog and fires a SwingWorker to update the
     * server, if appropriate.
     *
     *
     * @param repository
     */
    protected void editRepository(final RepositoryModel repository) {
src/main/java/com/gitblit/client/RepositoriesTableModel.java
@@ -27,9 +27,9 @@
/**
 * Table model of a list of repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class RepositoriesTableModel extends AbstractTableModel {
@@ -85,11 +85,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        Columns col = Columns.values()[columnIndex];
        switch (col) {
src/main/java/com/gitblit/client/SearchDialog.java
@@ -51,9 +51,9 @@
/**
 * The search dialog allows searching of a repository branch. This matches the
 * search implementation of the site.
 *
 *
 * @author James Moger
 *
 *
 */
public class SearchDialog extends JFrame {
@@ -103,6 +103,7 @@
        prev.setToolTipText(Translation.get("gb.pagePrevious"));
        prev.setEnabled(false);
        prev.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                search(--page);
            }
@@ -112,6 +113,7 @@
        next.setToolTipText(Translation.get("gb.pageNext"));
        next.setEnabled(false);
        next.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                search(++page);
            }
@@ -119,6 +121,7 @@
        final JButton search = new JButton(Translation.get(isSearch ? "gb.search" : "gb.refresh"));
        search.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                search(0);
            }
@@ -127,6 +130,7 @@
        final JButton viewCommit = new JButton(Translation.get("gb.view"));
        viewCommit.setEnabled(false);
        viewCommit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                viewCommit();
            }
@@ -135,6 +139,7 @@
        final JButton viewCommitDiff = new JButton(Translation.get("gb.commitdiff"));
        viewCommitDiff.setEnabled(false);
        viewCommitDiff.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                viewCommitDiff();
            }
@@ -143,6 +148,7 @@
        final JButton viewTree = new JButton(Translation.get("gb.tree"));
        viewTree.setEnabled(false);
        viewTree.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                viewTree();
            }
@@ -171,6 +177,7 @@
        table.getColumn(name).setCellRenderer(new MessageRenderer());
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    if (e.isControlDown()) {
@@ -199,6 +206,7 @@
        repositorySelector.setRenderer(nameRenderer);
        repositorySelector.setForeground(nameRenderer.getForeground());
        repositorySelector.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                // repopulate the branch list based on repository selection
                // preserve branch selection, if possible
@@ -234,6 +242,7 @@
        searchFragment = new JTextField(25);
        searchFragment.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                search(0);
            }
src/main/java/com/gitblit/client/SettingCellRenderer.java
@@ -27,9 +27,9 @@
/**
 * SettingModel cell renderer that indicates if a setting is the default or
 * modified.
 *
 *
 * @author James Moger
 *
 *
 */
public class SettingCellRenderer extends DefaultTableCellRenderer {
@@ -44,6 +44,7 @@
        modifiedFont = defaultFont.deriveFont(Font.BOLD);
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
src/main/java/com/gitblit/client/SettingPanel.java
@@ -33,7 +33,7 @@
/**
 * This panel displays the metadata for a particular setting.
 *
 *
 * @author James Moger
 */
public class SettingPanel extends JPanel {
src/main/java/com/gitblit/client/SettingsPanel.java
@@ -50,9 +50,9 @@
/**
 * Settings panel displays a list of server settings and their associated
 * metadata. This panel also allows editing of a setting.
 *
 *
 * @author James Moger
 *
 *
 */
public class SettingsPanel extends JPanel {
@@ -79,6 +79,7 @@
    private void initialize() {
        JButton refreshSettings = new JButton(Translation.get("gb.refresh"));
        refreshSettings.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshSettings();
            }
@@ -86,6 +87,7 @@
        final JButton editSetting = new JButton(Translation.get("gb.edit"));
        editSetting.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                int viewRow = table.getSelectedRow();
                int modelRow = table.convertRowIndexToModel(viewRow);
@@ -125,6 +127,7 @@
            }
        });
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    int viewRow = table.getSelectedRow();
@@ -137,11 +140,13 @@
        filterTextfield = new JTextField();
        filterTextfield.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                filterSettings(filterTextfield.getText());
            }
        });
        filterTextfield.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                filterSettings(filterTextfield.getText());
            }
@@ -166,7 +171,7 @@
        add(settingsTablePanel, BorderLayout.CENTER);
        add(settingsControls, BorderLayout.SOUTH);
    }
    @Override
    public void requestFocus() {
        filterTextfield.requestFocus();
@@ -192,6 +197,7 @@
            return;
        }
        RowFilter<SettingsTableModel, Object> containsFilter = new RowFilter<SettingsTableModel, Object>() {
            @Override
            public boolean include(Entry<? extends SettingsTableModel, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
src/main/java/com/gitblit/client/SettingsTableModel.java
@@ -26,9 +26,9 @@
/**
 * Table model of Map<String, SettingModel>.
 *
 *
 * @author James Moger
 *
 *
 */
public class SettingsTableModel extends AbstractTableModel {
@@ -89,11 +89,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (Columns.Value.ordinal() == columnIndex) {
            return SettingModel.class;
src/main/java/com/gitblit/client/StatusPanel.java
@@ -38,7 +38,7 @@
/**
 * This panel displays the server status.
 *
 *
 * @author James Moger
 */
public class StatusPanel extends JPanel {
@@ -65,6 +65,7 @@
    private void initialize() {
        JButton refreshStatus = new JButton(Translation.get("gb.refresh"));
        refreshStatus.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshStatus();
            }
@@ -153,7 +154,7 @@
        ServerStatus status = gitblit.getStatus();
        header.setText(Translation.get("gb.status"));
        version.setText(Constants.NAME + (status.isGO ? " GO v" : " WAR v") + status.version);
        releaseDate.setText(status.releaseDate);
        releaseDate.setText(status.releaseDate);
        bootDate.setText(status.bootDate.toString() + " (" + Translation.getTimeUtils().timeAgo(status.bootDate)
                + ")");
        url.setText(gitblit.url);
src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java
@@ -25,9 +25,9 @@
/**
 * Displays a subscribed icon on the left of the repository name, if there is at
 * least one subscribed branch.
 *
 *
 * @author James Moger
 *
 *
 */
public class SubscribedRepositoryRenderer extends NameRenderer {
@@ -46,6 +46,7 @@
        subscribedIcon = new ImageIcon(getClass().getResource("/bullet_feed.png"));
    }
    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
            boolean hasFocus, int row, int column) {
        super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
src/main/java/com/gitblit/client/SubscriptionsDialog.java
@@ -40,9 +40,9 @@
/**
 * Displays a list of repository branches and allows the user to check or
 * uncheck branches.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class SubscriptionsDialog extends JDialog {
@@ -68,6 +68,7 @@
        KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        JRootPane rootPane = new JRootPane();
        rootPane.registerKeyboardAction(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                setVisible(false);
            }
@@ -111,6 +112,7 @@
        final JButton cancel = new JButton(Translation.get("gb.cancel"));
        cancel.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                setVisible(false);
            }
@@ -118,6 +120,7 @@
        final JButton save = new JButton(Translation.get("gb.save"));
        save.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                save();
            }
@@ -141,6 +144,7 @@
            private static final long serialVersionUID = 1L;
            @Override
            public Insets getInsets() {
                return insets;
            }
src/main/java/com/gitblit/client/TeamsPanel.java
@@ -47,9 +47,9 @@
/**
 * Users panel displays a list of user accounts and allows management of those
 * accounts.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class TeamsPanel extends JPanel {
@@ -76,6 +76,7 @@
    private void initialize() {
        JButton refreshTeams = new JButton(Translation.get("gb.refresh"));
        refreshTeams.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshTeams();
            }
@@ -83,6 +84,7 @@
        JButton createTeam = new JButton(Translation.get("gb.create"));
        createTeam.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                createTeam();
            }
@@ -91,6 +93,7 @@
        final JButton editTeam = new JButton(Translation.get("gb.edit"));
        editTeam.setEnabled(false);
        editTeam.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                editTeam(getSelectedTeams().get(0));
            }
@@ -99,6 +102,7 @@
        final JButton delTeam = new JButton(Translation.get("gb.delete"));
        delTeam.setEnabled(false);
        delTeam.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                deleteTeams(getSelectedTeams());
            }
@@ -136,6 +140,7 @@
        });
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    editTeam(getSelectedTeams().get(0));
@@ -145,11 +150,13 @@
        filterTextfield = new JTextField();
        filterTextfield.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                filterTeams(filterTextfield.getText());
            }
        });
        filterTextfield.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                filterTeams(filterTextfield.getText());
            }
@@ -204,6 +211,7 @@
            return;
        }
        RowFilter<TeamsTableModel, Object> containsFilter = new RowFilter<TeamsTableModel, Object>() {
            @Override
            public boolean include(Entry<? extends TeamsTableModel, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
@@ -247,7 +255,7 @@
    /**
     * Displays the create team dialog and fires a SwingWorker to update the
     * server, if appropriate.
     *
     *
     */
    protected void createTeam() {
        EditTeamDialog dialog = new EditTeamDialog(gitblit.getProtocolVersion(),
@@ -296,7 +304,7 @@
    /**
     * Displays the edit team dialog and fires a SwingWorker to update the
     * server, if appropriate.
     *
     *
     * @param user
     */
    protected void editTeam(final TeamModel team) {
src/main/java/com/gitblit/client/TeamsTableModel.java
@@ -25,9 +25,9 @@
/**
 * Table model of a list of teams.
 *
 *
 * @author James Moger
 *
 *
 */
public class TeamsTableModel extends AbstractTableModel {
@@ -79,11 +79,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }
src/main/java/com/gitblit/client/Translation.java
@@ -22,14 +22,14 @@
/**
 * Loads the Gitblit language resource file.
 *
 *
 * @author James Moger
 *
 *
 */
public class Translation {
    private final static ResourceBundle translation;
    private final static TimeUtils timeUtils;
    static {
@@ -42,7 +42,7 @@
            bundle = ResourceBundle.getBundle("GitBlitWebApp");
        }
        translation = bundle;
        timeUtils = new TimeUtils(translation, null);
    }
@@ -52,7 +52,7 @@
        }
        return key;
    }
    public static TimeUtils getTimeUtils() {
        return timeUtils;
    }
src/main/java/com/gitblit/client/UsersPanel.java
@@ -48,9 +48,9 @@
/**
 * Users panel displays a list of user accounts and allows management of those
 * accounts.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class UsersPanel extends JPanel {
@@ -77,6 +77,7 @@
    private void initialize() {
        JButton refreshUsers = new JButton(Translation.get("gb.refresh"));
        refreshUsers.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                refreshUsers();
            }
@@ -84,6 +85,7 @@
        JButton createUser = new JButton(Translation.get("gb.create"));
        createUser.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                createUser();
            }
@@ -92,6 +94,7 @@
        final JButton editUser = new JButton(Translation.get("gb.edit"));
        editUser.setEnabled(false);
        editUser.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                editUser(getSelectedUsers().get(0));
            }
@@ -100,6 +103,7 @@
        final JButton delUser = new JButton(Translation.get("gb.delete"));
        delUser.setEnabled(false);
        delUser.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                deleteUsers(getSelectedUsers());
            }
@@ -111,7 +115,7 @@
        table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
        String name = table.getColumnName(UsersTableModel.Columns.Name.ordinal());
        table.getColumn(name).setCellRenderer(nameRenderer);
        int w = 130;
        name = table.getColumnName(UsersTableModel.Columns.Type.ordinal());
        table.getColumn(name).setMinWidth(w);
@@ -122,7 +126,7 @@
        name = table.getColumnName(UsersTableModel.Columns.Repositories.ordinal());
        table.getColumn(name).setMinWidth(w);
        table.getColumn(name).setMaxWidth(w);
        table.setRowSorter(defaultSorter);
        table.getRowSorter().toggleSortOrder(UsersTableModel.Columns.Name.ordinal());
        table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@@ -140,6 +144,7 @@
        });
        table.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                if (e.getClickCount() == 2) {
                    editUser(getSelectedUsers().get(0));
@@ -149,11 +154,13 @@
        filterTextfield = new JTextField();
        filterTextfield.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                filterUsers(filterTextfield.getText());
            }
        });
        filterTextfield.addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                filterUsers(filterTextfield.getText());
            }
@@ -191,7 +198,7 @@
    }
    protected abstract void updateTeamsTable();
    protected void updateTable(boolean pack) {
        tableModel.list.clear();
        tableModel.list.addAll(gitblit.getUsers());
@@ -208,6 +215,7 @@
            return;
        }
        RowFilter<UsersTableModel, Object> containsFilter = new RowFilter<UsersTableModel, Object>() {
            @Override
            public boolean include(Entry<? extends UsersTableModel, ? extends Object> entry) {
                for (int i = entry.getValueCount() - 1; i >= 0; i--) {
                    if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
@@ -251,7 +259,7 @@
    /**
     * Displays the create user dialog and fires a SwingWorker to update the
     * server, if appropriate.
     *
     *
     */
    protected void createUser() {
        EditUserDialog dialog = new EditUserDialog(gitblit.getProtocolVersion(),
@@ -300,7 +308,7 @@
    /**
     * Displays the edit user dialog and fires a SwingWorker to update the
     * server, if appropriate.
     *
     *
     * @param user
     */
    protected void editUser(final UserModel user) {
src/main/java/com/gitblit/client/UsersTableModel.java
@@ -25,9 +25,9 @@
/**
 * Table model of a list of users.
 *
 *
 * @author James Moger
 *
 *
 */
public class UsersTableModel extends AbstractTableModel {
@@ -83,11 +83,12 @@
    /**
     * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
     *
     *
     * @param columnIndex
     *            the column being queried
     * @return the Object.class
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }
src/main/java/com/gitblit/client/Utils.java
@@ -51,14 +51,14 @@
    public static JTable newTable(TableModel model, String datePattern) {
        return newTable(model, datePattern, null);
    }
    public static JTable newTable(TableModel model, String datePattern, final RowRenderer rowRenderer) {
        JTable table;
        if (rowRenderer == null) {
            table = new JTable(model);
        } else {
            table = new JTable(model) {
                @Override
                public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
                    Component c = super.prepareRenderer(renderer, row, column);
@@ -166,7 +166,7 @@
            showException(null, x);
        }
    }
    public static abstract class RowRenderer {
        public abstract void prepareRow(Component c, boolean isSelected, int row, int column);
    }
src/main/java/com/gitblit/fanout/FanoutClient.java
@@ -39,7 +39,7 @@
/**
 * Fanout client class.
 *
 *
 * @author James Moger
 *
 */
@@ -57,24 +57,26 @@
    private volatile Selector selector;
    private volatile SocketChannel socketCh;
    private Thread clientThread;
    private final AtomicBoolean isConnected;
    private final AtomicBoolean isRunning;
    private final AtomicBoolean isAutomaticReconnect;
    private final ByteBuffer writeBuffer;
    private final ByteBuffer readBuffer;
    private final CharsetDecoder decoder;
    private final Set<String> subscriptions;
    private boolean resubscribe;
    public interface FanoutListener {
        public void pong(Date timestamp);
        public void announcement(String channel, String message);
    }
    public static class FanoutAdapter implements FanoutListener {
        @Override
        public void pong(Date timestamp) { }
        @Override
        public void announcement(String channel, String message) { }
    }
@@ -86,20 +88,20 @@
            public void pong(Date timestamp) {
                System.out.println("Pong. " + timestamp);
            }
            @Override
            public void announcement(String channel, String message) {
                System.out.println(MessageFormat.format("Here ye, Here ye. {0} says {1}", channel, message));
            }
        });
        client.start();
        Thread.sleep(5000);
        client.ping();
        client.subscribe("james");
        client.announce("james", "12345");
        client.announce("james", "12345");
        client.subscribe("c52f99d16eb5627877ae957df7ce1be102783bd5");
        while (true) {
            Thread.sleep(10000);
            client.ping();
@@ -126,11 +128,11 @@
    public void removeListener(FanoutListener listener) {
        listeners.remove(listener);
    }
    public boolean isAutomaticReconnect() {
        return isAutomaticReconnect.get();
    }
    public void setAutomaticReconnect(boolean value) {
        isAutomaticReconnect.set(value);
    }
@@ -144,21 +146,21 @@
        confirmConnection();
        write("status");
    }
    public void subscribe(String channel) {
        confirmConnection();
        if (subscriptions.add(channel)) {
            write("subscribe " + channel);
        }
    }
    public void unsubscribe(String channel) {
        confirmConnection();
        if (subscriptions.remove(channel)) {
            write("unsubscribe " + channel);
        }
    }
    public void announce(String channel, String message) {
        confirmConnection();
        write("announce " + channel + " " + message);
@@ -169,11 +171,11 @@
            throw new RuntimeException("Fanout client is disconnected!");
        }
    }
    public boolean isConnected() {
        return isRunning.get() && socketCh != null && isConnected.get();
    }
    /**
     * Start client connection and return immediately.
     */
@@ -185,13 +187,13 @@
        clientThread = new Thread(this, "Fanout client");
        clientThread.start();
    }
    /**
     * Start client connection and wait until it has connected.
     */
    public void startSynchronously() {
        start();
        while (!isConnected()) {
        while (!isConnected()) {
            try {
                Thread.sleep(100);
            } catch (Exception e) {
@@ -221,8 +223,8 @@
    @Override
    public void run() {
        resetState();
        isRunning.set(true);
        isRunning.set(true);
        while (isRunning.get()) {
            // (re)connect
            if (socketCh == null) {
@@ -231,7 +233,7 @@
                    socketCh = SocketChannel.open(new InetSocketAddress(addr, port));
                    socketCh.configureBlocking(false);
                    selector = Selector.open();
                    id = FanoutConstants.getLocalSocketId(socketCh.socket());
                    id = FanoutConstants.getLocalSocketId(socketCh.socket());
                    socketCh.register(selector, SelectionKey.OP_READ);
                } catch (Exception e) {
                    logger.error(MessageFormat.format("failed to open client connection to {0}:{1,number,0}", host, port), e);
@@ -242,7 +244,7 @@
                    continue;
                }
            }
            // read/write
            try {
                selector.select(clientTimeout);
@@ -251,7 +253,7 @@
                while (i.hasNext()) {
                    SelectionKey key = i.next();
                    i.remove();
                    if (key.isReadable()) {
                        // read message
                        String content = read();
@@ -266,7 +268,7 @@
                        // resubscribe
                        if (resubscribe) {
                            resubscribe = false;
                            logger.info(MessageFormat.format("fanout client {0} re-subscribing to {1} channels", id, subscriptions.size()));
                            logger.info(MessageFormat.format("fanout client {0} re-subscribing to {1} channels", id, subscriptions.size()));
                            for (String subscription : subscriptions) {
                                write("subscribe " + subscription);
                            }
@@ -276,25 +278,25 @@
                }
            } catch (IOException e) {
                logger.error(MessageFormat.format("fanout client {0} error: {1}", id, e.getMessage()));
                closeChannel();
                closeChannel();
                if (!isAutomaticReconnect.get()) {
                    isRunning.set(false);
                    continue;
                }
            }
        }
        closeChannel();
        resetState();
    }
    protected void resetState() {
        readBuffer.clear();
        writeBuffer.clear();
        isRunning.set(false);
        isConnected.set(false);
    }
    private void closeChannel() {
        try {
            if (socketCh != null) {
@@ -315,7 +317,7 @@
                long time = Long.parseLong(fields[0]);
                Date date = new Date(time);
                firePong(date);
            } catch (Exception e) {
            } catch (Exception e) {
            }
            return true;
        } else if (fields.length == 2) {
@@ -366,7 +368,7 @@
            }
        }
    }
    protected synchronized String read() throws IOException {
        readBuffer.clear();
        long len = socketCh.read(readBuffer);
@@ -382,7 +384,7 @@
            return content;
        }
    }
    protected synchronized boolean write(String message) {
        try {
            logger.info(MessageFormat.format("fanout client {0} > {1}", id, message));
src/main/java/com/gitblit/fanout/FanoutConstants.java
@@ -25,11 +25,11 @@
    public final static String CH_DEBUG = "debug";
    public final static String MSG_CONNECTED = "connected...";
    public final static String MSG_BUSY = "busy";
    public static String getRemoteSocketId(Socket socket) {
        return socket.getInetAddress().getHostAddress() + ":" + socket.getPort();
    }
    public static String getLocalSocketId(Socket socket) {
        return socket.getInetAddress().getHostAddress() + ":" + socket.getLocalPort();
    }
src/main/java/com/gitblit/fanout/FanoutNioService.java
@@ -44,7 +44,7 @@
 *
 * This implementation uses channels and selectors, which are the Java analog of
 * the Linux epoll mechanism used in the original fanout C code.
 *
 *
 * @author James Moger
 *
 */
@@ -54,7 +54,7 @@
    private volatile ServerSocketChannel serviceCh;
    private volatile Selector selector;
    public static void main(String[] args) throws Exception {
        FanoutNioService pubsub = new FanoutNioService(null, DEFAULT_PORT);
        pubsub.setStrictRequestTermination(false);
@@ -64,7 +64,7 @@
    /**
     * Create a single-threaded fanout service.
     *
     *
     * @param host
     * @param port
     *            the port for running the fanout PubSub service
@@ -73,10 +73,10 @@
    public FanoutNioService(int port) {
        this(null, port);
    }
    /**
     * Create a single-threaded fanout service.
     *
     *
     * @param bindInterface
     *            the ip address to bind for the service, may be null
     * @param port
@@ -86,7 +86,7 @@
    public FanoutNioService(String bindInterface, int port) {
        super(bindInterface, port, "Fanout nio service");
    }
    @Override
    protected boolean isConnected() {
        return serviceCh != null;
@@ -102,10 +102,10 @@
                serviceCh.socket().bind(host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port));
                selector = Selector.open();
                serviceCh.register(selector, SelectionKey.OP_ACCEPT);
                logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
                logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
                        name, host == null ? "0.0.0.0" : host, port));
            } catch (IOException e) {
                logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
                logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
                        name, name, host == null ? "0.0.0.0" : host, port), e);
                return false;
            }
@@ -122,11 +122,11 @@
                for (Map.Entry<String, SocketChannel> client : clients.entrySet()) {
                    closeClientSocket(client.getKey(), client.getValue());
                }
                // close service socket channel
                logger.debug(MessageFormat.format("closing {0} socket channel", name));
                serviceCh.socket().close();
                serviceCh.close();
                serviceCh.close();
                serviceCh = null;
                selector.close();
                selector = null;
@@ -142,7 +142,7 @@
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyItr = keys.iterator();
            while (keyItr.hasNext()) {
                SelectionKey key = (SelectionKey) keyItr.next();
                SelectionKey key = keyItr.next();
                if (key.isAcceptable()) {
                    // new fanout client connection
                    ServerSocketChannel sch = (ServerSocketChannel) key.channel();
@@ -213,7 +213,7 @@
            }
        }
    }
    protected void closeClientSocket(String id, SocketChannel ch) {
        try {
            ch.close();
@@ -221,10 +221,11 @@
            logger.error(MessageFormat.format("fanout connection {0}", id), e);
        }
    }
    @Override
    protected void broadcast(Collection<FanoutServiceConnection> connections, String channel, String message) {
        super.broadcast(connections, channel, message);
        // register queued write
        Map<String, SocketChannel> sockets = getCurrentClientSockets();
        for (FanoutServiceConnection connection : connections) {
@@ -241,7 +242,7 @@
            }
        }
    }
    protected Map<String, SocketChannel> getCurrentClientSockets() {
        Map<String, SocketChannel> sockets = new HashMap<String, SocketChannel>();
        for (SelectionKey key : selector.keys()) {
@@ -253,11 +254,11 @@
        }
        return sockets;
    }
    /**
     * FanoutNioConnection handles reading/writing messages from a remote fanout
     * connection.
     *
     *
     * @author James Moger
     *
     */
@@ -276,7 +277,7 @@
            replyQueue = new ArrayList<String>();
            decoder = Charset.forName(FanoutConstants.CHARSET).newDecoder();
        }
        protected void read(SocketChannel ch, boolean strictRequestTermination) throws CharacterCodingException, IOException {
            long bytesRead = 0;
            readBuffer.clear();
@@ -293,7 +294,7 @@
            String [] lines = req.split(strictRequestTermination ? "\n" : "\n|\r");
            requestQueue.addAll(Arrays.asList(lines));
        }
        protected void write(SocketChannel ch) throws IOException {
            Iterator<String> itr = replyQueue.iterator();
            while (itr.hasNext()) {
@@ -306,7 +307,7 @@
                    writeBuffer.put((byte) 0xa);
                }
                writeBuffer.flip();
                // loop until write buffer has been completely sent
                int written = 0;
                int toWrite = writeBuffer.remaining();
@@ -316,7 +317,7 @@
                        Thread.sleep(10);
                    } catch (Exception x) {
                    }
                }
                }
                itr.remove();
            }
            writeBuffer.clear();
src/main/java/com/gitblit/fanout/FanoutService.java
@@ -37,29 +37,29 @@
/**
 * Base class for Fanout service implementations.
 *
 *
 * Subclass implementations can be used as a Sparkleshare PubSub notification
 * server.  This allows Sparkleshare to be used in conjunction with Gitblit
 * behind a corporate firewall that restricts or prohibits client internet access
 * to the default Sparkleshare PubSub server: notifications.sparkleshare.org
 *
 *
 * @author James Moger
 *
 */
public abstract class FanoutService implements Runnable {
    private final static Logger logger = LoggerFactory.getLogger(FanoutService.class);
    public final static int DEFAULT_PORT = 17000;
    protected final static int serviceTimeout = 5000;
    protected final String host;
    protected final int port;
    protected final String name;
    protected final String name;
    private Thread serviceThread;
    private final Map<String, FanoutServiceConnection> connections;
    private final Map<String, Set<FanoutServiceConnection>> subscriptions;
@@ -67,7 +67,7 @@
    private final AtomicBoolean strictRequestTermination;
    private final AtomicBoolean allowAllChannelAnnouncements;
    private final AtomicInteger concurrentConnectionLimit;
    private final Date bootDate;
    private final AtomicLong rejectedConnectionCount;
    private final AtomicInteger peakConnectionCount;
@@ -82,16 +82,16 @@
        this.host = host;
        this.port = port;
        this.name = name;
        connections = new ConcurrentHashMap<String, FanoutServiceConnection>();
        subscriptions = new ConcurrentHashMap<String, Set<FanoutServiceConnection>>();
        subscriptions.put(FanoutConstants.CH_ALL, new ConcurrentSkipListSet<FanoutServiceConnection>());
        isRunning = new AtomicBoolean(false);
        strictRequestTermination = new AtomicBoolean(false);
        allowAllChannelAnnouncements = new AtomicBoolean(false);
        concurrentConnectionLimit = new AtomicInteger(0);
        bootDate = new Date();
        rejectedConnectionCount = new AtomicLong(0);
        peakConnectionCount = new AtomicInteger(0);
@@ -106,18 +106,18 @@
    /*
     * Abstract methods
     */
    protected abstract boolean isConnected();
    protected abstract boolean connect();
    protected abstract void listen() throws IOException;
    protected abstract void disconnect();
    /**
     * Returns true if the service requires \n request termination.
     *
     *
     * @return true if request requires \n termination
     */
    public boolean isStrictRequestTermination() {
@@ -128,62 +128,62 @@
     * Control the termination of fanout requests. If true, fanout requests must
     * be terminated with \n. If false, fanout requests may be terminated with
     * \n, \r, \r\n, or \n\r. This is useful for debugging with a telnet client.
     *
     *
     * @param isStrictTermination
     */
    public void setStrictRequestTermination(boolean isStrictTermination) {
        strictRequestTermination.set(isStrictTermination);
    }
    /**
     * Returns the maximum allowable concurrent fanout connections.
     *
     *
     * @return the maximum allowable concurrent connection count
     */
    public int getConcurrentConnectionLimit() {
        return concurrentConnectionLimit.get();
    }
    /**
     * Sets the maximum allowable concurrent fanout connection count.
     *
     *
     * @param value
     */
    public void setConcurrentConnectionLimit(int value) {
        concurrentConnectionLimit.set(value);
    }
    /**
     * Returns true if connections are allowed to announce on the all channel.
     *
     *
     * @return true if connections are allowed to announce on the all channel
     */
    public boolean allowAllChannelAnnouncements() {
        return allowAllChannelAnnouncements.get();
    }
    /**
     * Allows/prohibits connections from announcing on the ALL channel.
     *
     *
     * @param value
     */
    public void setAllowAllChannelAnnouncements(boolean value) {
        allowAllChannelAnnouncements.set(value);
    }
    /**
     * Returns the current connections
     *
     *
     * @param channel
     * @return map of current connections keyed by their id
     */
    public Map<String, FanoutServiceConnection> getCurrentConnections() {
        return connections;
    }
    /**
     * Returns all subscriptions
     *
     *
     * @return map of current subscriptions keyed by channel name
     */
    public Map<String, Set<FanoutServiceConnection>> getCurrentSubscriptions() {
@@ -192,7 +192,7 @@
    /**
     * Returns the subscriptions for the specified channel
     *
     *
     * @param channel
     * @return set of subscribed connections for the specified channel
     */
@@ -202,17 +202,17 @@
    /**
     * Returns the runtime statistics object for this service.
     *
     *
     * @return stats
     */
    public FanoutStats getStatistics() {
        FanoutStats stats = new FanoutStats();
        // settings
        stats.allowAllChannelAnnouncements = allowAllChannelAnnouncements();
        stats.concurrentConnectionLimit = getConcurrentConnectionLimit();
        stats.strictRequestTermination = isStrictRequestTermination();
        // runtime stats
        stats.bootDate = bootDate;
        stats.rejectedConnectionCount = rejectedConnectionCount.get();
@@ -222,16 +222,16 @@
        stats.totalMessages = totalMessages.get();
        stats.totalSubscribes = totalSubscribes.get();
        stats.totalUnsubscribes = totalUnsubscribes.get();
        stats.totalPings = totalPings.get();
        stats.totalPings = totalPings.get();
        stats.currentConnections = connections.size();
        stats.currentChannels = subscriptions.size();
        stats.currentSubscriptions = subscriptions.size() * connections.size();
        return stats;
    }
    /**
     * Returns true if the service is ready.
     *
     *
     * @return true, if the service is ready
     */
    public boolean isReady() {
@@ -243,7 +243,7 @@
    /**
     * Start the Fanout service thread and immediatel return.
     *
     *
     */
    public void start() {
        if (isRunning.get()) {
@@ -254,10 +254,10 @@
        serviceThread.setName(MessageFormat.format("{0} {1}:{2,number,0}", name, host == null ? "all" : host, port));
        serviceThread.start();
    }
    /**
     * Start the Fanout service thread and wait until it is accepting connections.
     *
     *
     */
    public void startSynchronously() {
        start();
@@ -268,7 +268,7 @@
            }
        }
    }
    /**
     * Stop the Fanout service.  This method returns when the service has been
     * completely shutdown.
@@ -290,7 +290,7 @@
        }
        logger.info(MessageFormat.format("stopped {0}", name));
    }
    /**
     * Main execution method of the service
     */
@@ -314,10 +314,10 @@
                }
            }
        }
        disconnect();
        disconnect();
        resetState();
    }
    protected void resetState() {
        // reset state data
        connections.clear();
@@ -334,23 +334,23 @@
    /**
     * Configure the client connection socket.
     *
     *
     * @param socket
     * @throws SocketException
     */
    protected void configureClientSocket(Socket socket) throws SocketException {
        socket.setKeepAlive(true);
        socket.setKeepAlive(true);
        socket.setSoLinger(true, 0); // immediately discard any remaining data
    }
    /**
     * Add the connection to the connections map.
     *
     *
     * @param connection
     * @return false if the connection was rejected due to too many concurrent
     *         connections
     */
    protected boolean addConnection(FanoutServiceConnection connection) {
    protected boolean addConnection(FanoutServiceConnection connection) {
        int limit = getConcurrentConnectionLimit();
        if (limit > 0 && connections.size() > limit) {
            logger.info(MessageFormat.format("hit {0,number,0} connection limit, rejecting fanout connection", concurrentConnectionLimit));
@@ -358,7 +358,7 @@
            connection.busy();
            return false;
        }
        // add the connection to our map
        connections.put(connection.id, connection);
@@ -371,10 +371,10 @@
        connection.connected();
        return true;
    }
    /**
     * Remove the connection from the connections list and from subscriptions.
     *
     *
     * @param connection
     */
    protected void removeConnection(FanoutServiceConnection connection) {
@@ -393,46 +393,46 @@
        }
        logger.info(MessageFormat.format("fanout connection {0} removed", connection.id));
    }
    /**
     * Tests to see if the connection is being monitored by the service.
     *
     *
     * @param connection
     * @return true if the service is monitoring the connection
     */
    protected boolean hasConnection(FanoutServiceConnection connection) {
        return connections.containsKey(connection.id);
    }
    /**
     * Reply to a connection on the specified channel.
     *
     *
     * @param connection
     * @param channel
     * @param message
     * @return the reply
     */
    protected String reply(FanoutServiceConnection connection, String channel, String message) {
    protected String reply(FanoutServiceConnection connection, String channel, String message) {
        if (channel != null && channel.length() > 0) {
            increment(totalMessages);
        }
        return connection.reply(channel, message);
    }
    /**
     * Service method to broadcast a message to all connections.
     *
     *
     * @param message
     */
    public void broadcastAll(String message) {
        broadcast(connections.values(), FanoutConstants.CH_ALL, message);
        increment(totalAnnouncements);
    }
    /**
     * Service method to broadcast a message to connections subscribed to the
     * channel.
     *
     *
     * @param message
     */
    public void broadcast(String channel, String message) {
@@ -440,10 +440,10 @@
        broadcast(connections, channel, message);
        increment(totalAnnouncements);
    }
    /**
     * Broadcast a message to connections subscribed to the specified channel.
     *
     *
     * @param connections
     * @param channel
     * @param message
@@ -453,10 +453,10 @@
            reply(connection, channel, message);
        }
    }
    /**
     * Process an incoming Fanout request.
     *
     *
     * @param connection
     * @param req
     * @return the reply to the request, may be null
@@ -476,10 +476,10 @@
        }
        return null;
    }
    /**
     * Process the Fanout request.
     *
     *
     * @param connection
     * @param action
     * @param channel
@@ -535,7 +535,7 @@
        }
        return null;
    }
    private String asHexArray(String req) {
        StringBuilder sb = new StringBuilder();
        for (char c : req.toCharArray()) {
@@ -543,10 +543,10 @@
        }
        return "[ " + sb.toString().trim() + " ]";
    }
    /**
     * Increment a long and prevent negative rollover.
     *
     *
     * @param counter
     */
    private void increment(AtomicLong counter) {
@@ -555,7 +555,7 @@
            counter.set(0);
        }
    }
    @Override
    public String toString() {
        return name;
src/main/java/com/gitblit/fanout/FanoutServiceConnection.java
@@ -24,14 +24,14 @@
/**
 * FanoutServiceConnection handles reading/writing messages from a remote fanout
 * connection.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class FanoutServiceConnection implements Comparable<FanoutServiceConnection> {
    private static final Logger logger = LoggerFactory.getLogger(FanoutServiceConnection.class);
    public final String id;
    protected FanoutServiceConnection(Socket socket) {
@@ -42,25 +42,25 @@
    /**
     * Send the connection a debug channel connected message.
     *
     *
     * @param message
     */
    protected void connected() {
        reply(FanoutConstants.CH_DEBUG, FanoutConstants.MSG_CONNECTED);
    }
    /**
     * Send the connection a debug channel busy message.
     *
     *
     * @param message
     */
    protected void busy() {
        reply(FanoutConstants.CH_DEBUG, FanoutConstants.MSG_BUSY);
    }
    /**
     * Send the connection a message for the specified channel.
     *
     *
     * @param channel
     * @param message
     * @return the reply
src/main/java/com/gitblit/fanout/FanoutSocketService.java
@@ -34,7 +34,7 @@
 * This implementation creates a master acceptor thread which accepts incoming
 * fanout connections and then spawns a daemon thread for each accepted connection.
 * If there are 100 concurrent fanout connections, there are 101 threads.
 *
 *
 * @author James Moger
 *
 */
@@ -50,10 +50,10 @@
        pubsub.setAllowAllChannelAnnouncements(false);
        pubsub.start();
    }
    /**
     * Create a multi-threaded fanout service.
     *
     *
     * @param port
     *            the port for running the fanout PubSub service
     * @throws IOException
@@ -64,7 +64,7 @@
    /**
     * Create a multi-threaded fanout service.
     *
     *
     * @param bindInterface
     *            the ip address to bind for the service, may be null
     * @param port
@@ -74,12 +74,12 @@
    public FanoutSocketService(String bindInterface, int port) {
        super(bindInterface, port, "Fanout socket service");
    }
    @Override
    protected boolean isConnected() {
        return serviceSocket != null;
    }
    @Override
    protected boolean connect() {
        if (serviceSocket == null) {
@@ -88,7 +88,7 @@
                serviceSocket.setReuseAddress(true);
                serviceSocket.setSoTimeout(serviceTimeout);
                serviceSocket.bind(host == null ? new InetSocketAddress(port) : new InetSocketAddress(host, port));
                logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
                logger.info(MessageFormat.format("{0} is ready on {1}:{2,number,0}",
                        name, host == null ? "0.0.0.0" : host, serviceSocket.getLocalPort()));
            } catch (IOException e) {
                logger.error(MessageFormat.format("failed to open {0} on {1}:{2,number,0}",
@@ -140,17 +140,17 @@
            // ignore accept timeout exceptions
        }
    }
    /**
     * FanoutSocketConnection handles reading/writing messages from a remote fanout
     * connection.
     *
     *
     * @author James Moger
     *
     */
    class FanoutSocketConnection extends FanoutServiceConnection implements Runnable {
        Socket socket;
        FanoutSocketConnection(Socket socket) {
            super(socket);
            this.socket = socket;
@@ -205,7 +205,7 @@
            logger.info(MessageFormat.format("thread for fanout connection {0} is finished", id));
        }
        @Override
        protected void reply(String content) throws IOException {
            // synchronously send reply
@@ -218,7 +218,7 @@
            }
            os.flush();
        }
        protected void closeConnection() {
            // close the connection socket
            try {
@@ -226,7 +226,7 @@
            } catch (IOException e) {
            }
            socket = null;
            // remove this connection from the service
            removeConnection(this);
        }
src/main/java/com/gitblit/fanout/FanoutStats.java
@@ -21,18 +21,18 @@
/**
 * Encapsulates the runtime stats of a fanout service.
 *
 *
 * @author James Moger
 *
 */
public class FanoutStats implements Serializable {
    private static final long serialVersionUID = 1L;
    public long concurrentConnectionLimit;
    public boolean allowAllChannelAnnouncements;
    public boolean strictRequestTermination;
    public Date bootDate;
    public long rejectedConnectionCount;
    public int peakConnectionCount;
@@ -45,7 +45,7 @@
    public long totalSubscribes;
    public long totalUnsubscribes;
    public long totalPings;
    public String info() {
        int i = 0;
        StringBuilder sb = new StringBuilder();
@@ -67,14 +67,14 @@
        sb.append(infoInt(i++, "total pings"));
        String template = sb.toString();
        String info = MessageFormat.format(template,
        String info = MessageFormat.format(template,
                bootDate.toString(),
                Boolean.toString(strictRequestTermination),
                Boolean.toString(allowAllChannelAnnouncements),
                concurrentConnectionLimit,
                rejectedConnectionCount,
                peakConnectionCount,
                currentConnections,
                currentConnections,
                currentChannels,
                currentSubscriptions,
                currentSubscriptions == 0 ? 0 : (currentSubscriptions - currentConnections),
@@ -86,11 +86,11 @@
                        totalPings);
        return info;
    }
    private String infoStr(int index, String label) {
        return label + ": {" + index + "}\n";
    }
    private String infoInt(int index, String label) {
        return label + ": {" + index + ",number,0}\n";
    }
src/main/java/com/gitblit/git/GitDaemon.java
@@ -73,9 +73,9 @@
/**
 * Gitblit's Git Daemon ignores any and all per-repository daemon settings and
 * integrates into Gitblit's security model.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitDaemon {
@@ -113,7 +113,7 @@
    /**
     * Construct the Gitblit Git daemon.
     *
     *
     * @param bindInterface
     *            the ip address of the interface to bind
     * @param port
@@ -128,10 +128,10 @@
        // set the repository resolver and pack factories
        repositoryResolver = new RepositoryResolver<GitDaemonClient>(folder);
    }
    /**
     * Configure a new daemon for the specified network address.
     *
     *
     * @param addr
     *            address to listen for connections on. If null, any available
     *            port will be chosen on all network interfaces.
@@ -177,11 +177,11 @@
                    }
                } };
    }
    public int getPort() {
        return myAddress.getPort();
    }
    public String formatUrl(String servername, String repository) {
        if (getPort() == 9418) {
            // standard port
@@ -199,7 +199,7 @@
    /**
     * Set the timeout before willing to abort an IO call.
     *
     *
     * @param seconds
     *            number of seconds to wait (with no data transfer occurring)
     *            before aborting an IO read or write operation with the
@@ -211,7 +211,7 @@
    /**
     * Start this daemon on a background thread.
     *
     *
     * @throws IOException
     *             the server socket could not be opened.
     * @throws IllegalStateException
@@ -228,6 +228,7 @@
        run.set(true);
        acceptSocket = listenSock;
        acceptThread = new Thread(processors, "Git-Daemon-Accept") {
            @Override
            public void run() {
                while (isRunning()) {
                    try {
@@ -250,7 +251,7 @@
            }
        };
        acceptThread.start();
        logger.info(MessageFormat.format("Git Daemon is listening on {0}:{1,number,0}", myAddress.getAddress().getHostAddress(), myAddress.getPort()));
    }
@@ -290,6 +291,7 @@
            dc.setRemoteAddress(((InetSocketAddress) peer).getAddress());
        new Thread(processors, "Git-Daemon-Client " + peer.toString()) {
            @Override
            public void run() {
                try {
                    dc.execute(s);
src/main/java/com/gitblit/git/GitDaemonClient.java
@@ -65,7 +65,7 @@
    private InputStream rawIn;
    private OutputStream rawOut;
    private String repositoryName;
    GitDaemonClient(final GitDaemon d) {
@@ -95,11 +95,11 @@
    public OutputStream getOutputStream() {
        return rawOut;
    }
    public void setRepositoryName(String repositoryName) {
        this.repositoryName = repositoryName;
    }
    /** @return the name of the requested repository. */
    public String getRepositoryName() {
        return repositoryName;
src/main/java/com/gitblit/git/GitDaemonService.java
@@ -68,6 +68,7 @@
    GitDaemonService(final String cmdName, final String cfgName) {
        command = cmdName.startsWith("git-") ? cmdName : "git-" + cmdName; //$NON-NLS-1$ //$NON-NLS-2$
        configKey = new SectionParser<ServiceConfig>() {
            @Override
            public ServiceConfig parse(final Config cfg) {
                return new ServiceConfig(GitDaemonService.this, cfg, cfgName);
            }
src/main/java/com/gitblit/git/GitServlet.java
@@ -24,9 +24,9 @@
/**
 * The GitServlet provides http/https access to Git repositories.
 * Access to this servlet is protected by the GitFilter.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitServlet extends org.eclipse.jgit.http.server.GitServlet {
src/main/java/com/gitblit/git/GitblitReceivePack.java
@@ -106,7 +106,7 @@
        setAllowCreates(user.canCreateRef(repository));
        setAllowDeletes(user.canDeleteRef(repository));
        setAllowNonFastForwards(user.canRewindRef(repository));
        // setup pre and post receive hook
        setPreReceiveHook(this);
        setPostReceiveHook(this);
src/main/java/com/gitblit/git/GitblitUploadPackFactory.java
@@ -36,9 +36,9 @@
/**
 * The upload pack factory creates an upload pack which controls what refs are
 * advertised to cloning/pulling clients.
 *
 *
 * @author James Moger
 *
 *
 * @param <X> the connection type
 */
public class GitblitUploadPackFactory<X> implements UploadPackFactory<X> {
@@ -51,7 +51,7 @@
        int timeout = 0;
        if (req instanceof HttpServletRequest) {
            // http/https request may or may not be authenticated
            // http/https request may or may not be authenticated
            user = GitBlit.self().authenticate((HttpServletRequest) req);
            if (user == null) {
                user = UserModel.ANONYMOUS;
@@ -67,7 +67,7 @@
        UploadPack up = new UploadPack(db);
        up.setRefFilter(refFilter);
        up.setTimeout(timeout);
        return up;
    }
@@ -76,13 +76,13 @@
     * requesting user.
     */
    public static class UserRefFilter implements RefFilter {
        final UserModel user;
        public UserRefFilter(UserModel user) {
            this.user = user;
        }
        @Override
        public Map<String, Ref> filter(Map<String, Ref> refs) {
            if (user.canAdmin()) {
src/main/java/com/gitblit/git/RepositoryResolver.java
@@ -34,14 +34,14 @@
/**
 * Resolves repositories and grants export access.
 *
 *
 * @author James Moger
 *
 */
public class RepositoryResolver<X> extends FileResolver<X> {
    private final Logger logger = LoggerFactory.getLogger(RepositoryResolver.class);
    public RepositoryResolver(File repositoriesFolder) {
        super(repositoriesFolder, true);
    }
@@ -53,7 +53,7 @@
    public Repository open(final X req, final String name)
            throws RepositoryNotFoundException, ServiceNotEnabledException {
        Repository repo = super.open(req, name);
        // Set repository name for the pack factories
        // We do this because the JGit API does not have a consistent way to
        // retrieve the repository name from the pack factories or the hooks.
@@ -68,7 +68,7 @@
        }
        return repo;
    }
    /**
     * Check if this repository can be served by the requested client connection.
     */
@@ -79,7 +79,7 @@
        String scheme = null;
        UserModel user = null;
        String origin = null;
        if (req instanceof GitDaemonClient) {
            // git daemon request
            // this is an anonymous/unauthenticated protocol
@@ -90,7 +90,7 @@
        } else if (req instanceof HttpServletRequest) {
            // http/https request
            HttpServletRequest httpRequest = (HttpServletRequest) req;
            scheme = httpRequest.getScheme();
            scheme = httpRequest.getScheme();
            origin = httpRequest.getRemoteAddr();
            user = GitBlit.self().authenticate(httpRequest);
            if (user == null) {
@@ -104,7 +104,7 @@
                    scheme, repositoryName, user.username, origin));
            return true;
        }
        // user can not access this git repo
        logger.warn(MessageFormat.format("{0}:// access of {1} by {2} from {3} DENIED",
                scheme, repositoryName, user.username, origin));
src/main/java/com/gitblit/models/Activity.java
@@ -35,7 +35,7 @@
/**
 * Model class to represent the commit activity across many repositories. This
 * class is used by the Activity page.
 *
 *
 * @author James Moger
 */
public class Activity implements Serializable, Comparable<Activity> {
@@ -45,7 +45,7 @@
    public final Date startDate;
    public final Date endDate;
    private final Set<RepositoryCommit> commits;
    private final Map<String, Metric> authorMetrics;
@@ -56,7 +56,7 @@
    /**
     * Constructor for one day of activity.
     *
     *
     * @param date
     */
    public Activity(Date date) {
@@ -65,7 +65,7 @@
    /**
     * Constructor for specified duration of activity from start date.
     *
     *
     * @param date
     *            the start date of the activity
     * @param duration
@@ -79,10 +79,10 @@
        repositoryMetrics = new HashMap<String, Metric>();
        authorExclusions = new TreeSet<String>();
    }
    /**
     * Exclude the specified authors from the metrics.
     *
     *
     * @param authors
     */
    public void excludeAuthors(Collection<String> authors) {
@@ -94,7 +94,7 @@
    /**
     * Adds a commit to the activity object as long as the commit is not a
     * duplicate.
     *
     *
     * @param repository
     * @param branch
     * @param commit
@@ -109,7 +109,7 @@
    /**
     * Adds a commit to the activity object as long as the commit is not a
     * duplicate.
     *
     *
     * @param repository
     * @param branch
     * @param commit
@@ -140,7 +140,7 @@
    public int getCommitCount() {
        return commits.size();
    }
    public List<RepositoryCommit> getCommits() {
        List<RepositoryCommit> list = new ArrayList<RepositoryCommit>(commits);
        Collections.sort(list);
src/main/java/com/gitblit/models/AnnotatedLine.java
@@ -24,9 +24,9 @@
/**
 * AnnotatedLine is a serializable model class that represents a the most recent
 * author, date, and commit id of a line in a source file.
 *
 *
 * @author James Moger
 *
 *
 */
public class AnnotatedLine implements Serializable {
src/main/java/com/gitblit/models/DailyLogEntry.java
@@ -25,7 +25,7 @@
 * Model class to simulate a push for presentation in the push log news feed
 * for a repository that does not have a Gitblit push log.  Commits are grouped
 * by date and may be additionally split by ref.
 *
 *
 * @author James Moger
 */
public class DailyLogEntry extends RefLogEntry implements Serializable {
@@ -45,7 +45,7 @@
        if (getAuthorCount() == 1) {
            return getCommits().get(0).getCommitterIdent();
        }
        return super.getCommitterIdent();
    }
@@ -54,20 +54,21 @@
        if (getAuthorCount() == 1) {
            return getCommits().get(0).getAuthorIdent();
        }
        return super.getAuthorIdent();
    }
    /**
     * Tracks the change type for the specified ref.
     *
     *
     * @param ref
     * @param type
     * @param oldId
     * @param newId
     */
    @Override
    public void updateRef(String ref, ReceiveCommand.Type type, String oldId, String newId) {
        // daily digests are filled from most recent to oldest
        // daily digests are filled from most recent to oldest
        String preservedNewId = getNewId(ref);
        if (preservedNewId == null) {
            // no preserved new id, this is newest commit
src/main/java/com/gitblit/models/FederationModel.java
@@ -30,7 +30,7 @@
 * Gitblit instance to pull the repositories and configuration from another
 * Gitblit instance. This is a backup operation and can be considered something
 * like svn-sync.
 *
 *
 */
public class FederationModel implements Serializable, Comparable<FederationModel> {
@@ -45,7 +45,7 @@
    public String frequency;
    public String folder;
    public boolean bare;
    public boolean mirror;
@@ -68,7 +68,7 @@
    /**
     * The constructor for a remote server configuration.
     *
     *
     * @param serverName
     */
    public FederationModel(String serverName) {
@@ -109,7 +109,7 @@
    /**
     * Updates the pull status of a particular repository in this federation
     * registration.
     *
     *
     * @param repository
     * @param status
     */
@@ -133,7 +133,7 @@
    /**
     * Iterates over the current pull results and returns the lowest pull
     * status.
     *
     *
     * @return the lowest pull status of the registration
     */
    public FederationPullStatus getLowestStatus() {
@@ -152,7 +152,7 @@
    /**
     * Returns true if this registration represents the result data sent by a
     * pulling Gitblit instance.
     *
     *
     * @return true, if this is result data
     */
    public boolean isResultData() {
@@ -181,7 +181,7 @@
    /**
     * Class that encapsulates a point-in-time pull result.
     *
     *
     */
    public static class RepositoryStatus implements Serializable, Comparable<RepositoryStatus> {
src/main/java/com/gitblit/models/FederationProposal.java
@@ -37,14 +37,14 @@
    public FederationToken tokenType;
    public String token;
    public String message;
    public Map<String, RepositoryModel> repositories;
    /**
     * The constructor for a federation proposal.
     *
     *
     * @param url
     *            the url of the source Gitblit instance
     * @param tokenType
src/main/java/com/gitblit/models/FederationSet.java
@@ -37,7 +37,7 @@
    /**
     * The constructor for a federation set.
     *
     *
     * @param name
     *            the name of this federation set
     * @param tokenType
src/main/java/com/gitblit/models/FeedEntryModel.java
@@ -21,7 +21,7 @@
/**
 * FeedEntryModel represents an entry in a syndication (RSS) feed.
 *
 *
 * @author James Moger
 */
public class FeedEntryModel implements Serializable, Comparable<FeedEntryModel> {
src/main/java/com/gitblit/models/FeedModel.java
@@ -22,7 +22,7 @@
/**
 * FeedModel represents a syndication (RSS) feed.
 *
 *
 * @author James Moger
 */
public class FeedModel implements Serializable, Comparable<FeedModel> {
src/main/java/com/gitblit/models/ForkModel.java
@@ -24,44 +24,44 @@
/**
 * A ForkModel represents a repository, its direct descendants, and its origin.
 *
 *
 * @author James Moger
 *
 */
public class ForkModel implements Serializable {
    private static final long serialVersionUID = 1L;
    public final RepositoryModel repository;
    public final List<ForkModel> forks;
    public ForkModel(RepositoryModel repository) {
        this.repository = repository;
        this.forks = new ArrayList<ForkModel>();
    }
    public boolean isRoot() {
        return StringUtils.isEmpty(repository.originRepository);
    }
    public boolean isNode() {
        return !ArrayUtils.isEmpty(forks);
    }
    public boolean isLeaf() {
        return ArrayUtils.isEmpty(forks);
    }
    public boolean isPersonalRepository() {
        return repository.isPersonalRepository();
    }
    @Override
    public int hashCode() {
        return repository.hashCode();
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof ForkModel) {
@@ -69,7 +69,7 @@
        }
        return false;
    }
    @Override
    public String toString() {
        return repository.toString();
src/main/java/com/gitblit/models/GitClientApplication.java
@@ -23,7 +23,7 @@
/**
 * Model class to represent a git client application.
 *
 *
 * @author James Moger
 *
 */
@@ -60,18 +60,18 @@
        }
        return false;
    }
    public boolean supportsTransport(String transportOrUrl) {
        if (ArrayUtils.isEmpty(transports)) {
            return true;
        }
        String scheme = transportOrUrl;
        if (transportOrUrl.indexOf(':') > -1) {
            // strip scheme
            scheme = transportOrUrl.substring(0, transportOrUrl.indexOf(':'));
        }
        for (String transport : transports) {
            if (transport.equalsIgnoreCase(scheme)) {
                return true;
@@ -79,7 +79,7 @@
        }
        return false;
    }
    @Override
    public String toString() {
        return StringUtils.isEmpty(title) ? name : title;
src/main/java/com/gitblit/models/GitNote.java
@@ -21,9 +21,9 @@
 * GitNote is a serializable model class that represents a git note. This class
 * retains an instance of the RefModel which contains the commit in which this
 * git note was created.
 *
 *
 * @author James Moger
 *
 *
 */
public class GitNote implements Serializable {
src/main/java/com/gitblit/models/GravatarProfile.java
@@ -20,9 +20,9 @@
/**
 * Represents a Gravatar profile.
 *
 *
 * @author James Moger
 *
 *
 */
public class GravatarProfile implements Serializable {
@@ -35,7 +35,7 @@
    public String preferredUsername;
    public String currentLocation;
    public String aboutMe;
    public String profileUrl;
    public String thumbnailUrl;
    public List<ProfileObject> photos;
src/main/java/com/gitblit/models/Metric.java
@@ -20,9 +20,9 @@
/**
 * Metric is a serializable model class that encapsulates metrics for some given
 * type.
 *
 *
 * @author James Moger
 *
 *
 */
public class Metric implements Serializable, Comparable<Metric> {
src/main/java/com/gitblit/models/PathModel.java
@@ -24,9 +24,9 @@
/**
 * PathModel is a serializable model class that represents a file or a folder,
 * including all its metadata and associated commit id.
 *
 *
 * @author James Moger
 *
 *
 */
public class PathModel implements Serializable, Comparable<PathModel> {
@@ -56,7 +56,7 @@
    public boolean isSubmodule() {
        return FileMode.GITLINK.equals(mode);
    }
    public boolean isTree() {
        return FileMode.TREE.equals(mode);
    }
@@ -105,26 +105,26 @@
    /**
     * PathChangeModel is a serializable class that represents a file changed in
     * a commit.
     *
     *
     * @author James Moger
     *
     *
     */
    public static class PathChangeModel extends PathModel {
        private static final long serialVersionUID = 1L;
        public ChangeType changeType;
        public int insertions;
        public int deletions;
        public PathChangeModel(String name, String path, long size, int mode, String objectId,
                String commitId, ChangeType type) {
            super(name, path, size, mode, objectId, commitId);
            this.changeType = type;
        }
        public void update(char op) {
            switch (op) {
            case '+':
src/main/java/com/gitblit/models/ProjectModel.java
@@ -26,9 +26,9 @@
/**
 * ProjectModel is a serializable model class.
 *
 *
 * @author James Moger
 *
 *
 */
public class ProjectModel implements Serializable, Comparable<ProjectModel> {
@@ -39,7 +39,7 @@
    public String title;
    public String description;
    public final Set<String> repositories = new HashSet<String>();
    public String projectMarkdown;
    public String repositoriesMarkdown;
    public Date lastChange;
@@ -48,7 +48,7 @@
    public ProjectModel(String name) {
        this(name, false);
    }
    public ProjectModel(String name, boolean isRoot) {
        this.name = name;
        this.isRoot = isRoot;
@@ -56,7 +56,7 @@
        this.title = "";
        this.description = "";
    }
    public boolean isUserProject() {
        return ModelUtils.isPersonalRepository(name);
    }
@@ -80,16 +80,16 @@
        for (String name:names) {
            repositories.add(name.toLowerCase());
        }
    }
    }
    public void removeRepository(String name) {
        repositories.remove(name.toLowerCase());
    }
    public String getDisplayName() {
        return StringUtils.isEmpty(title) ? name : title;
    }
    @Override
    public String toString() {
        return name;
src/main/java/com/gitblit/models/RefLogEntry.java
@@ -36,7 +36,7 @@
/**
 * Model class to represent a push into a repository.
 *
 *
 * @author James Moger
 */
public class RefLogEntry implements Serializable, Comparable<RefLogEntry> {
@@ -44,22 +44,22 @@
    private static final long serialVersionUID = 1L;
    public final String repository;
    public final Date date;
    public final UserModel user;
    private final Set<RepositoryCommit> commits;
    protected final Map<String, ReceiveCommand.Type> refUpdates;
    protected final Map<String, String> refIdChanges;
    private int authorCount;
    /**
     * Constructor for specified duration of push from start date.
     *
     *
     * @param repository
     *            the repository that received the push
     * @param date
@@ -76,10 +76,10 @@
        this.refIdChanges = new HashMap<String, String>();
        this.authorCount = -1;
    }
    /**
     * Tracks the change type for the specified ref.
     *
     *
     * @param ref
     * @param type
     */
@@ -88,10 +88,10 @@
            refUpdates.put(ref, type);
        }
    }
    /**
     * Tracks the change type for the specified ref.
     *
     *
     * @param ref
     * @param type
     * @param oldId
@@ -103,10 +103,10 @@
            refIdChanges.put(ref, oldId + "-" + newId);
        }
    }
    /**
     * Returns the old id of a ref.
     *
     *
     * @param ref
     * @return the old id
     */
@@ -120,7 +120,7 @@
    /**
     * Returns the new id of a ref
     *
     *
     * @param ref
     * @return the new id
     */
@@ -131,10 +131,10 @@
        }
        return change.split("-")[1];
    }
    /**
     * Returns the change type of the ref change.
     *
     *
     * @param ref
     * @return the change type for the ref
     */
@@ -146,7 +146,7 @@
    /**
     * Adds a commit to the push entry object as long as the commit is not a
     * duplicate.
     *
     *
     * @param branch
     * @param commit
     * @return a RepositoryCommit, if one was added. Null if this is duplicate
@@ -164,7 +164,7 @@
    /**
     * Adds a commit to the push entry object as long as the commit is not a
     * duplicate.
     *
     *
     * @param branch
     * @param commit
     * @return a RepositoryCommit, if one was added. Null if this is duplicate
@@ -181,17 +181,17 @@
    /**
     * Adds a a list of repository commits.  This is used to construct discrete
     * ref push log entries
     *
     *
     * @param commits
     */
    public void addCommits(List<RepositoryCommit> list) {
        commits.addAll(list);
        authorCount = -1;
    }
    /**
     * Returns true if this push contains a non-fastforward ref update.
     *
     *
     * @return true if this is a non-fastforward push
     */
    public boolean isNonFastForward() {
@@ -202,10 +202,10 @@
        }
        return false;
    }
    /**
     * Returns true if this ref has been rewound.
     *
     *
     * @param ref
     * @return true if this is a non-fastforward ref update
     */
@@ -219,7 +219,7 @@
    /**
     * Returns true if this ref has been deleted.
     *
     *
     * @param ref
     * @return true if this is a delete ref update
     */
@@ -230,28 +230,28 @@
        }
        return ReceiveCommand.Type.DELETE.equals(type);
    }
    /**
     * Returns the list of refs changed by the push.
     *
     *
     * @return a list of refs
     */
    public List<String> getChangedRefs() {
        return new ArrayList<String>(refUpdates.keySet());
    }
    /**
     * Returns the list of branches changed by the push.
     *
     *
     * @return a list of branches
     */
    public List<String> getChangedBranches() {
        return getChangedRefs(Constants.R_HEADS);
    }
    /**
     * Returns the list of tags changed by the push.
     *
     *
     * @return a list of tags
     */
    public List<String> getChangedTags() {
@@ -260,7 +260,7 @@
    /**
     * Gets the changed refs in the push.
     *
     *
     * @param baseRef
     * @return the changed refs
     */
@@ -275,7 +275,7 @@
        Collections.sort(list);
        return list;
    }
    public int getAuthorCount() {
        if (authorCount == -1) {
            Set<String> authors = new HashSet<String>();
@@ -287,19 +287,19 @@
        }
        return authorCount;
    }
    /**
     * The total number of commits in the push.
     *
     *
     * @return the number of commits in the push
     */
    public int getCommitCount() {
        return commits.size();
    }
    /**
     * Returns all commits in the push.
     *
     *
     * @return a list of commits
     */
    public List<RepositoryCommit> getCommits() {
@@ -307,10 +307,10 @@
        Collections.sort(list);
        return list;
    }
    /**
     * Returns all commits that belong to a particular ref
     *
     *
     * @param ref
     * @return a list of commits
     */
@@ -324,7 +324,7 @@
        Collections.sort(list);
        return list;
    }
    public PersonIdent getCommitterIdent() {
        return new PersonIdent(user.getDisplayName(), user.emailAddress == null ? user.username : user.emailAddress);
    }
@@ -341,7 +341,7 @@
        // reverse chronological order
        return o.date.compareTo(date);
    }
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
src/main/java/com/gitblit/models/RefModel.java
@@ -28,9 +28,9 @@
/**
 * RefModel is a serializable model class that represents a tag or branch and
 * includes the referenced object.
 *
 *
 * @author James Moger
 *
 *
 */
public class RefModel implements Serializable, Comparable<RefModel> {
src/main/java/com/gitblit/models/RegistrantAccessPermission.java
@@ -24,7 +24,7 @@
/**
 * Represents a Registrant-AccessPermission tuple.
 *
 *
 * @author James Moger
 */
public class RegistrantAccessPermission implements Serializable, Comparable<RegistrantAccessPermission> {
@@ -37,7 +37,7 @@
    public PermissionType permissionType;
    public boolean mutable;
    public String source;
    public RegistrantAccessPermission() {
    }
@@ -46,7 +46,7 @@
        this.permissionType = PermissionType.EXPLICIT;
        this.mutable = true;
    }
    public RegistrantAccessPermission(String registrant, AccessPermission permission, PermissionType permissionType, RegistrantType registrantType, String source, boolean mutable) {
        this.registrant = registrant;
        this.permission = permission;
@@ -55,7 +55,7 @@
        this.source = source;
        this.mutable = mutable;
    }
    public boolean isAdmin() {
        return PermissionType.ADMINISTRATOR.equals(permissionType);
    }
@@ -63,7 +63,7 @@
    public boolean isOwner() {
        return PermissionType.OWNER.equals(permissionType);
    }
    public boolean isExplicit() {
        return PermissionType.EXPLICIT.equals(permissionType);
    }
@@ -79,7 +79,7 @@
    public boolean isMissing() {
        return PermissionType.MISSING.equals(permissionType);
    }
    public int getScore() {
        switch (registrantType) {
        case REPOSITORY:
@@ -102,7 +102,7 @@
            return 0;
        }
    }
    @Override
    public int compareTo(RegistrantAccessPermission p) {
        switch (registrantType) {
@@ -113,7 +113,7 @@
            int score2 = p.getScore();
            if (score1 <= 2 && score2 <= 2) {
                // group admin, owner, and explicit together
                return StringUtils.compareRepositoryNames(registrant, p.registrant);
                return StringUtils.compareRepositoryNames(registrant, p.registrant);
            }
            if (score1 < score2) {
                return -1;
@@ -126,22 +126,22 @@
            return registrant.toLowerCase().compareTo(p.registrant.toLowerCase());
        }
    }
    @Override
    public int hashCode() {
        return registrant.hashCode();
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof RegistrantAccessPermission) {
            RegistrantAccessPermission p = (RegistrantAccessPermission) o;
            return registrant.equals(p.registrant);
        }
        return false;
    }
    @Override
    public String toString() {
        return permission.asRole(registrant);
src/main/java/com/gitblit/models/RepositoryCommit.java
@@ -27,7 +27,7 @@
/**
 * Model class to represent a RevCommit, it's source repository, and the branch.
 * This class is used by the activity page.
 *
 *
 * @author James Moger
 */
public class RepositoryCommit implements Serializable, Comparable<RepositoryCommit> {
@@ -71,7 +71,7 @@
    public String getShortMessage() {
        return commit.getShortMessage();
    }
    public Date getCommitDate() {
        return new Date(commit.getCommitTime() * 1000L);
    }
@@ -79,7 +79,7 @@
    public int getParentCount() {
        return commit.getParentCount();
    }
    public RevCommit [] getParents() {
        return commit.getParents();
    }
@@ -91,7 +91,7 @@
    public PersonIdent getCommitterIdent() {
        return commit.getCommitterIdent();
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof RepositoryCommit) {
@@ -116,14 +116,14 @@
        }
        return 0;
    }
    public RepositoryCommit clone(String withRef) {
        return new RepositoryCommit(repository, withRef, commit);
    }
    @Override
    public String toString() {
        return MessageFormat.format("{0} {1} {2,date,yyyy-MM-dd HH:mm} {3} {4}",
        return MessageFormat.format("{0} {1} {2,date,yyyy-MM-dd HH:mm} {3} {4}",
                getShortName(), branch, getCommitterIdent().getWhen(), getAuthorIdent().getName(),
                getShortMessage());
    }
src/main/java/com/gitblit/models/RepositoryModel.java
@@ -35,9 +35,9 @@
/**
 * RepositoryModel is a serializable model class that represents a Gitblit
 * repository including its configuration settings and access restriction.
 *
 *
 * @author James Moger
 *
 *
 */
public class RepositoryModel implements Serializable, Comparable<RepositoryModel> {
@@ -84,14 +84,14 @@
    public boolean verifyCommitter;
    public String gcThreshold;
    public int gcPeriod;
    public int maxActivityCommits;
    public int maxActivityCommits;
    public List<String> metricAuthorExclusions;
    public CommitMessageRenderer commitMessageRenderer;
    public transient boolean isCollectingGarbage;
    public Date lastGC;
    public String sparkleshareId;
    public RepositoryModel() {
        this("", "", "", new Date(0));
    }
@@ -103,14 +103,14 @@
        this.accessRestriction = AccessRestrictionType.NONE;
        this.authorizationControl = AuthorizationControl.NAMED;
        this.federationSets = new ArrayList<String>();
        this.federationStrategy = FederationStrategy.FEDERATE_THIS;
        this.federationStrategy = FederationStrategy.FEDERATE_THIS;
        this.projectPath = StringUtils.getFirstPathElement(name);
        this.owners = new ArrayList<String>();
        this.isBare = true;
        addOwner(owner);
    }
    public List<String> getLocalBranches() {
        if (ArrayUtils.isEmpty(availableRefs)) {
            return new ArrayList<String>();
@@ -123,30 +123,30 @@
        }
        return localBranches;
    }
    public void addFork(String repository) {
        if (forks == null) {
            forks = new TreeSet<String>();
        }
        forks.add(repository);
    }
    public void removeFork(String repository) {
        if (forks == null) {
            return;
        }
        forks.remove(repository);
    }
    public void resetDisplayName() {
        displayName = null;
    }
    @Override
    public int hashCode() {
        return name.hashCode();
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof RepositoryModel) {
@@ -167,38 +167,38 @@
    public int compareTo(RepositoryModel o) {
        return StringUtils.compareRepositoryNames(name, o.name);
    }
    public boolean isFork() {
        return !StringUtils.isEmpty(originRepository);
    }
    public boolean isOwner(String username) {
        if (StringUtils.isEmpty(username) || ArrayUtils.isEmpty(owners)) {
            return false;
        }
        return owners.contains(username.toLowerCase());
    }
    public boolean isPersonalRepository() {
        return !StringUtils.isEmpty(projectPath) && ModelUtils.isPersonalRepository(projectPath);
    }
    public boolean isUsersPersonalRepository(String username) {
        return !StringUtils.isEmpty(projectPath) && ModelUtils.isUsersPersonalRepository(username, projectPath);
    }
    public boolean allowAnonymousView() {
        return !accessRestriction.atLeast(AccessRestrictionType.VIEW);
    }
    public boolean isShowActivity() {
        return maxActivityCommits > -1;
    }
    public boolean isSparkleshared() {
        return !StringUtils.isEmpty(sparkleshareId);
    }
    public RepositoryModel cloneAs(String cloneName) {
        RepositoryModel clone = new RepositoryModel();
        clone.originRepository = name;
@@ -216,7 +216,7 @@
        clone.useTickets = useTickets;
        clone.skipSizeCalculation = skipSizeCalculation;
        clone.skipSummaryMetrics = skipSummaryMetrics;
        clone.sparkleshareId = sparkleshareId;
        clone.sparkleshareId = sparkleshareId;
        return clone;
    }
src/main/java/com/gitblit/models/RepositoryUrl.java
@@ -22,7 +22,7 @@
/**
 * Represents a git repository url and it's associated access permission for the
 * current user.
 *
 *
 * @author James Moger
 *
 */
@@ -37,7 +37,7 @@
        this.url = url;
        this.permission = permission;
    }
    public boolean isExternal() {
        return permission == null;
    }
src/main/java/com/gitblit/models/SearchResult.java
@@ -8,16 +8,16 @@
/**
 * Model class that represents a search result.
 *
 *
 * @author James Moger
 *
 *
 */
public class SearchResult implements Serializable {
    private static final long serialVersionUID = 1L;
    public int hitId;
    public int totalHits;
    public float score;
@@ -29,24 +29,24 @@
    public String committer;
    public String summary;
    public String fragment;
    public String repository;
    public String branch;
    public String commitId;
    public String path;
    public List<String> tags;
    public SearchObjectType type;
    public SearchResult() {
    }
    public String getId() {
        switch (type) {
        case blob:
src/main/java/com/gitblit/models/ServerSettings.java
@@ -26,7 +26,7 @@
 * setting metadata such as name, current value, default value, description, and
 * directives. It is a model class for serialization and presentation, but not
 * for persistence.
 *
 *
 * @author James Moger
 */
public class ServerSettings implements Serializable {
@@ -36,13 +36,13 @@
    private static final long serialVersionUID = 1L;
    public List<String> pushScripts;
    public boolean supportsCredentialChanges;
    public boolean supportsDisplayNameChanges;
    public boolean supportsEmailAddressChanges;
    public boolean supportsTeamMembershipChanges;
    public ServerSettings() {
@@ -62,7 +62,7 @@
    public SettingModel get(String key) {
        return settings.get(key);
    }
    public boolean hasKey(String key) {
        return settings.containsKey(key);
    }
src/main/java/com/gitblit/models/ServerStatus.java
@@ -25,9 +25,9 @@
/**
 * ServerStatus encapsulates runtime status information about the server
 * including some information about the system environment.
 *
 *
 * @author James Moger
 *
 *
 */
public class ServerStatus implements Serializable {
src/main/java/com/gitblit/models/SettingModel.java
@@ -26,7 +26,7 @@
/**
 * SettingModel represents a setting and all its metadata: name, current value,
 * default value, description, and directives.
 *
 *
 * @author James Moger
 */
public class SettingModel implements Serializable {
@@ -55,7 +55,7 @@
    /**
     * Returns true if the current value is the default value.
     *
     *
     * @return true if current value is the default value
     */
    public boolean isDefaultValue() {
@@ -66,7 +66,7 @@
    /**
     * Returns the boolean value for the currentValue. If the currentValue can
     * not be interpreted as a boolean, the defaultValue is returned.
     *
     *
     * @param defaultValue
     * @return key value or defaultValue
     */
@@ -80,7 +80,7 @@
    /**
     * Returns the integer value for the currentValue. If the currentValue can
     * not be interpreted as an integer, the defaultValue is returned.
     *
     *
     * @param defaultValue
     * @return key value or defaultValue
     */
@@ -97,7 +97,7 @@
    /**
     * Returns the char value for currentValue. If the currentValue can not be
     * interpreted as a char, the defaultValue is returned.
     *
     *
     * @param defaultValue
     * @return key value or defaultValue
     */
@@ -111,7 +111,7 @@
    /**
     * Returns the string value for currentValue. If the currentValue is null,
     * the defaultValue is returned.
     *
     *
     * @param defaultValue
     * @return key value or defaultValue
     */
@@ -124,7 +124,7 @@
    /**
     * Returns a list of space-separated strings from the specified key.
     *
     *
     * @return list of strings
     */
    public List<String> getStrings() {
@@ -134,7 +134,7 @@
    /**
     * Returns a list of strings from the currentValue using the specified
     * string separator.
     *
     *
     * @param separator
     * @return list of strings
     */
@@ -143,10 +143,10 @@
        strings = StringUtils.getStringsFromValue(currentValue, separator);
        return strings;
    }
    /**
     * Returns a map of strings from the current value.
     *
     *
     * @return map of string, string
     */
    public Map<String, String> getMap() {
@@ -154,7 +154,7 @@
        for (String string : getStrings()) {
            String[] kvp = string.split("=", 2);
            String key = kvp[0];
            String value = kvp[1];
            String value = kvp[1];
            map.put(key,  value);
        }
        return map;
src/main/java/com/gitblit/models/SubmoduleModel.java
@@ -20,9 +20,9 @@
/**
 * SubmoduleModel is a serializable model class that represents a git submodule
 * definition.
 *
 *
 * @author James Moger
 *
 *
 */
public class SubmoduleModel implements Serializable {
@@ -40,7 +40,8 @@
        this.path = path;
        this.url = url;
    }
    @Override
    public String toString() {
        return path + "=" + url;
    }
src/main/java/com/gitblit/models/TeamModel.java
@@ -35,9 +35,9 @@
/**
 * TeamModel is a serializable model class that represents a group of users and
 * a list of accessible repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class TeamModel implements Serializable, Comparable<TeamModel> {
@@ -77,7 +77,7 @@
    public void addRepository(String name) {
        addRepositoryPermission(name);
    }
    @Deprecated
    @Unused
    public void addRepositories(Collection<String> names) {
@@ -90,10 +90,10 @@
        removeRepositoryPermission(name);
    }
    /**
     * Returns a list of repository permissions for this team.
     *
     *
     * @return the team's list of permissions
     */
    public List<RegistrantAccessPermission> getRepositoryPermissions() {
@@ -117,11 +117,11 @@
        Collections.sort(list);
        return list;
    }
    /**
     * Returns true if the team has any type of specified access permission for
     * this repository.
     *
     *
     * @param name
     * @return true if team has a specified access permission for the repository
     */
@@ -143,11 +143,11 @@
        }
        return false;
    }
    /**
     * Returns true if the team has an explicitly specified access permission for
     * this repository.
     *
     *
     * @param name
     * @return if the team has an explicitly specified access permission
     */
@@ -155,7 +155,7 @@
        String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
        return permissions.containsKey(repository);
    }
    /**
     * Adds a repository permission to the team.
     * <p>
@@ -178,13 +178,13 @@
            addRepositoryPermission(role);
        }
    }
    public AccessPermission removeRepositoryPermission(String name) {
        String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
        repositories.remove(repository);
        return permissions.remove(repository);
    }
    public void setRepositoryPermission(String repository, AccessPermission permission) {
        if (permission == null) {
            // remove the permission
@@ -196,16 +196,16 @@
            repositories.add(repository.toLowerCase());
        }
    }
    public RegistrantAccessPermission getRepositoryPermission(RepositoryModel repository) {
        RegistrantAccessPermission ap = new RegistrantAccessPermission();
        ap.registrant = name;
        ap.registrantType = RegistrantType.TEAM;
        ap.permission = AccessPermission.NONE;
        ap.mutable = false;
        // determine maximum permission for the repository
        final AccessPermission maxPermission =
        final AccessPermission maxPermission =
                (repository.isFrozen || !repository.isBare) ?
                        AccessPermission.CLONE : AccessPermission.REWIND;
@@ -219,7 +219,7 @@
            }
            return ap;
        }
        if (canAdmin) {
            ap.permissionType = PermissionType.ADMINISTRATOR;
            if (AccessPermission.REWIND.atMost(maxPermission)) {
@@ -229,7 +229,7 @@
            }
            return ap;
        }
        if (permissions.containsKey(repository.name.toLowerCase())) {
            // exact repository permission specified
            AccessPermission p = permissions.get(repository.name.toLowerCase());
@@ -262,7 +262,7 @@
                }
            }
        }
        // still no explicit or regex, check for implicit permissions
        if (AccessPermission.NONE == ap.permission) {
            switch (repository.accessRestriction) {
@@ -289,7 +289,7 @@
        return ap;
    }
    protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
        if (repository.accessRestriction.atLeast(ifRestriction)) {
            RegistrantAccessPermission ap = getRepositoryPermission(repository);
@@ -297,7 +297,7 @@
        }
        return true;
    }
    public boolean canView(RepositoryModel repository) {
        return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
    }
src/main/java/com/gitblit/models/UserModel.java
@@ -42,16 +42,16 @@
 * UserModel is a serializable model class that represents a user and the user's
 * restricted repository memberships. Instances of UserModels are also used as
 * servlet user principals.
 *
 *
 * @author James Moger
 *
 *
 */
public class UserModel implements Principal, Serializable, Comparable<UserModel> {
    private static final long serialVersionUID = 1L;
    public static final UserModel ANONYMOUS = new UserModel();
    // field names are reflectively mapped in EditUser page
    public String username;
    public String password;
@@ -78,7 +78,7 @@
    public AccountType accountType;
    public UserPreferences userPreferences;
    public UserModel(String username) {
        this.username = username;
        this.isAuthenticated = true;
@@ -92,7 +92,7 @@
        this.accountType = AccountType.LOCAL;
        this.userPreferences = new UserPreferences(this.username);
    }
    public boolean isLocalAccount() {
        return accountType.isLocal();
    }
@@ -100,7 +100,7 @@
    /**
     * This method does not take into consideration Ownership where the
     * administrator has not explicitly granted access to the owner.
     *
     *
     * @param repositoryName
     * @return
     */
@@ -129,7 +129,7 @@
        }
        return false;
    }
    @Deprecated
    @Unused
    public boolean hasRepository(String name) {
@@ -147,11 +147,11 @@
    public void removeRepository(String name) {
        removeRepositoryPermission(name);
    }
    /**
     * Returns a list of repository permissions for this user exclusive of
     * permissions inherited from team memberships.
     *
     *
     * @return the user's list of permissions
     */
    public List<RegistrantAccessPermission> getRepositoryPermissions() {
@@ -178,7 +178,7 @@
            list.add(new RegistrantAccessPermission(registrant, ap, pType, RegistrantType.REPOSITORY, source, mutable));
        }
        Collections.sort(list);
        // include immutable team permissions, being careful to preserve order
        Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>(list);
        for (TeamModel team : teams) {
@@ -193,11 +193,11 @@
        }
        return new ArrayList<RegistrantAccessPermission>(set);
    }
    /**
     * Returns true if the user has any type of specified access permission for
     * this repository.
     *
     *
     * @param name
     * @return true if user has a specified access permission for the repository
     */
@@ -219,11 +219,11 @@
        }
        return false;
    }
    /**
     * Returns true if the user has an explicitly specified access permission for
     * this repository.
     *
     *
     * @param name
     * @return if the user has an explicitly specified access permission
     */
@@ -231,11 +231,11 @@
        String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
        return permissions.containsKey(repository);
    }
    /**
     * Returns true if the user's team memberships specify an access permission for
     * this repository.
     *
     *
     * @param name
     * @return if the user's team memberships specifi an access permission
     */
@@ -249,7 +249,7 @@
        }
        return false;
    }
    /**
     * Adds a repository permission to the team.
     * <p>
@@ -266,13 +266,13 @@
        repositories.add(repository);
        permissions.put(repository, permission);
    }
    public AccessPermission removeRepositoryPermission(String name) {
        String repository = AccessPermission.repositoryFromRole(name).toLowerCase();
        repositories.remove(repository);
        return permissions.remove(repository);
    }
    public void setRepositoryPermission(String repository, AccessPermission permission) {
        if (permission == null) {
            // remove the permission
@@ -289,9 +289,9 @@
        ap.registrantType = RegistrantType.USER;
        ap.permission = AccessPermission.NONE;
        ap.mutable = false;
        // determine maximum permission for the repository
        final AccessPermission maxPermission =
        final AccessPermission maxPermission =
                (repository.isFrozen || !repository.isBare) ?
                        AccessPermission.CLONE : AccessPermission.REWIND;
@@ -325,7 +325,7 @@
            }
            return ap;
        }
        // repository owner - either specified owner or personal repository
        if (repository.isOwner(username) || repository.isUsersPersonalRepository(username)) {
            ap.permissionType = PermissionType.OWNER;
@@ -336,7 +336,7 @@
            }
            return ap;
        }
        if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl) && isAuthenticated) {
            // AUTHENTICATED is a shortcut for authorizing all logged-in users RW+ access
            if (AccessPermission.REWIND.atMost(maxPermission)) {
@@ -346,7 +346,7 @@
            }
            return ap;
        }
        // explicit user permission OR user regex match is used
        // if that fails, then the best team permission is used
        if (permissions.containsKey(repository.name.toLowerCase())) {
@@ -381,7 +381,7 @@
                }
            }
        }
        // try to find a team match
        for (TeamModel team : teams) {
            RegistrantAccessPermission p = team.getRepositoryPermission(repository);
@@ -392,7 +392,7 @@
                ap.permissionType = PermissionType.TEAM;
            }
        }
        // still no explicit, regex, or team match, check for implicit permissions
        if (AccessPermission.NONE == ap.permission) {
            switch (repository.accessRestriction) {
@@ -416,10 +416,10 @@
                break;
            }
        }
        return ap;
    }
    protected boolean canAccess(RepositoryModel repository, AccessRestrictionType ifRestriction, AccessPermission requirePermission) {
        if (repository.accessRestriction.atLeast(ifRestriction)) {
            RegistrantAccessPermission ap = getRepositoryPermission(repository);
@@ -427,11 +427,11 @@
        }
        return true;
    }
    public boolean canView(RepositoryModel repository) {
        return canAccess(repository, AccessRestrictionType.VIEW, AccessPermission.VIEW);
    }
    public boolean canView(RepositoryModel repository, String ref) {
        // Default UserModel doesn't implement ref-level security.
        // Other Realms (i.e. Gerrit) may override this method.
@@ -486,19 +486,19 @@
        }
        return canClone(repository);
    }
    public boolean canDelete(RepositoryModel model) {
        return canAdmin() || model.isUsersPersonalRepository(username);
    }
    public boolean canEdit(RepositoryModel model) {
        return canAdmin() || model.isUsersPersonalRepository(username) || model.isOwner(username);
    }
    /**
     * This returns true if the user has fork privileges or the user has fork
     * privileges because of a team membership.
     *
     *
     * @return true if the user can fork
     */
    public boolean canFork() {
@@ -518,7 +518,7 @@
    /**
     * This returns true if the user has admin privileges or the user has admin
     * privileges because of a team membership.
     *
     *
     * @return true if the user can admin
     */
    public boolean canAdmin() {
@@ -538,7 +538,7 @@
    /**
     * This returns true if the user has create privileges or the user has create
     * privileges because of a team membership.
     *
     *
     * @return true if the user can admin
     */
    public boolean canCreate() {
@@ -554,10 +554,10 @@
        }
        return false;
    }
    /**
     * Returns true if the user is allowed to create the specified repository.
     *
     *
     * @param repository
     * @return true if the user can create the repository
     */
@@ -601,27 +601,27 @@
    public String getName() {
        return username;
    }
    public String getDisplayName() {
        if (StringUtils.isEmpty(displayName)) {
            return username;
        }
        return displayName;
    }
    public String getPersonalPath() {
        return ModelUtils.getPersonalPath(username);
    }
    public UserPreferences getPreferences() {
        return userPreferences;
    }
    @Override
    public int hashCode() {
        return username.hashCode();
    }
    @Override
    public boolean equals(Object o) {
        if (o instanceof UserModel) {
@@ -639,10 +639,10 @@
    public int compareTo(UserModel o) {
        return username.compareTo(o.username);
    }
    /**
     * Returns true if the name/email pair match this user account.
     *
     *
     * @param name
     * @param email
     * @return true, if the name and email address match this account
@@ -667,13 +667,13 @@
        }
        return nameVerified && emailVerified;
    }
    @Deprecated
    public boolean hasBranchPermission(String repositoryName, String branch) {
        // Default UserModel doesn't implement branch-level security. Other Realms (i.e. Gerrit) may override this method.
        return hasRepositoryPermission(repositoryName) || hasTeamRepositoryPermission(repositoryName);
    }
    public boolean isMyPersonalRepository(String repository) {
        String projectPath = StringUtils.getFirstPathElement(repository);
        return !StringUtils.isEmpty(projectPath) && projectPath.equalsIgnoreCase(getPersonalPath());
src/main/java/com/gitblit/models/UserPreferences.java
@@ -27,7 +27,7 @@
/**
 * User preferences.
 *
 *
 * @author James Moger
 *
 */
@@ -38,20 +38,20 @@
    public final String username;
    public String locale;
    private final Map<String, UserRepositoryPreferences> repositoryPreferences = new TreeMap<String, UserRepositoryPreferences>();
    public UserPreferences(String username) {
        this.username = username;
    }
    public Locale getLocale() {
        if (StringUtils.isEmpty(locale)) {
            return null;
        }
        return new Locale(locale);
    }
    public UserRepositoryPreferences getRepositoryPreferences(String repositoryName) {
        String key = repositoryName.toLowerCase();
        if (!repositoryPreferences.containsKey(key)) {
@@ -63,11 +63,11 @@
        }
        return repositoryPreferences.get(key);
    }
    public void setRepositoryPreferences(UserRepositoryPreferences pref) {
        repositoryPreferences.put(pref.repositoryName.toLowerCase(), pref);
    }
    public boolean isStarredRepository(String repository) {
        if (repositoryPreferences == null) {
            return false;
@@ -79,7 +79,7 @@
        }
        return false;
    }
    public List<String> getStarredRepositories() {
        List<String> list = new ArrayList<String>();
        for (UserRepositoryPreferences prefs : repositoryPreferences.values()) {
src/main/java/com/gitblit/models/UserRepositoryPreferences.java
@@ -19,7 +19,7 @@
/**
 * User repository preferences.
 *
 *
 * @author James Moger
 *
 */
@@ -28,11 +28,11 @@
    private static final long serialVersionUID = 1L;
    public String username;
    public String repositoryName;
    public boolean starred;
    @Override
    public String toString() {
        return username + ":" + repositoryName;
src/main/java/com/gitblit/utils/ActivityUtils.java
@@ -45,16 +45,16 @@
/**
 * Utility class for building activity information from repositories.
 *
 *
 * @author James Moger
 *
 *
 */
public class ActivityUtils {
    /**
     * Gets the recent activity from the repositories for the last daysBack days
     * on the specified branch.
     *
     *
     * @param models
     *            the list of repositories to query
     * @param daysBack
@@ -79,7 +79,7 @@
        df.setTimeZone(timezone);
        Calendar cal = Calendar.getInstance();
        cal.setTimeZone(timezone);
        // aggregate author exclusions
        Set<String> authorExclusions = new TreeSet<String>();
        authorExclusions.addAll(GitBlit.getStrings(Keys.web.metricAuthorExclusions));
@@ -125,7 +125,7 @@
                        // trim commits to maximum count
                        commits = commits.subList(0,  model.maxActivityCommits);
                    }
                    for (RepositoryCommit commit : commits) {
                    for (RepositoryCommit commit : commits) {
                        Date date = commit.getCommitDate();
                        String dateStr = df.format(date);
                        if (!activity.containsKey(dateStr)) {
@@ -142,7 +142,7 @@
                        activity.get(dateStr).addCommit(commit);
                    }
                }
                // close the repository
                repository.close();
            }
@@ -155,7 +155,7 @@
    /**
     * Returns the Gravatar profile, if available, for the specified email
     * address.
     *
     *
     * @param emailaddress
     * @return a Gravatar Profile
     * @throws IOException
@@ -167,7 +167,7 @@
    /**
     * Creates a Gravatar thumbnail url from the specified email address.
     *
     *
     * @param email
     *            address to query Gravatar
     * @param width
@@ -183,10 +183,10 @@
                "https://www.gravatar.com/avatar/{0}?s={1,number,0}&d=identicon", emailHash, width);
        return url;
    }
    /**
     * Creates a Gravatar thumbnail url from the specified email address.
     *
     *
     * @param email
     *            address to query Gravatar
     * @param width
@@ -206,7 +206,7 @@
    /**
     * Returns the Gravatar profile, if available, for the specified hashcode.
     * address.
     *
     *
     * @param hash
     *            the hash of the email address
     * @return a Gravatar Profile
src/main/java/com/gitblit/utils/ArrayUtils.java
@@ -22,9 +22,9 @@
/**
 * Utility class for arrays and collections.
 *
 *
 * @author James Moger
 *
 *
 */
public class ArrayUtils {
@@ -39,11 +39,11 @@
    public static boolean isEmpty(Object [] array) {
        return array == null || array.length == 0;
    }
    public static boolean isEmpty(Collection<?> collection) {
        return collection == null || collection.size() == 0;
    }
    public static String toString(Collection<?> collection) {
        if (isEmpty(collection)) {
            return "";
@@ -56,7 +56,7 @@
        sb.setLength(sb.length() - 2);
        return sb.toString();
    }
    public static Collection<String> fromString(String value) {
        if (StringUtils.isEmpty(value)) {
            value = "";
src/main/java/com/gitblit/utils/Base64.java
@@ -19,7 +19,7 @@
 * href="http://iharder.net/base64">http://iharder.net/base64</a> periodically
 * to check for updates or to contribute improvements.
 * </p>
 *
 *
 * @author Robert Harder
 * @author rob@iharder.net
 * @version 2.1, stripped to minimum feature set used by JGit.
@@ -89,7 +89,7 @@
     * <var>destOffset</var> + 4 for the <var>destination</var> array. The
     * actual number of significant bytes in your array is given by
     * <var>numSigBytes</var>.
     *
     *
     * @param source
     *            the array to convert
     * @param srcOffset
@@ -146,7 +146,7 @@
    /**
     * Encodes a byte array into Base64 notation.
     *
     *
     * @param source
     *            The data to convert
     * @return encoded base64 representation of source.
@@ -157,7 +157,7 @@
    /**
     * Encodes a byte array into Base64 notation.
     *
     *
     * @param source
     *            The data to convert
     * @param off
@@ -199,7 +199,7 @@
     * <var>destOffset</var> + 3 for the <var>destination</var> array. This
     * method returns the actual number of bytes that were converted from the
     * Base64 encoding.
     *
     *
     * @param source
     *            the array to convert
     * @param srcOffset
@@ -246,7 +246,7 @@
    /**
     * Low-level decoding ASCII characters from a byte array.
     *
     *
     * @param source
     *            The Base64 encoded data
     * @param off
@@ -294,7 +294,7 @@
    /**
     * Decodes data from Base64 notation.
     *
     *
     * @param s
     *            the string to decode
     * @return the decoded data
src/main/java/com/gitblit/utils/ByteFormat.java
@@ -23,9 +23,9 @@
/**
 * ByteFormat is a formatter which takes numbers and returns filesizes in bytes,
 * kilobytes, megabytes, or gigabytes.
 *
 *
 * @author James Moger
 *
 *
 */
public class ByteFormat extends Format {
@@ -38,6 +38,7 @@
        return format(Long.valueOf(value));
    }
    @Override
    public StringBuffer format(Object obj, StringBuffer buf, FieldPosition pos) {
        if (obj instanceof Number) {
            long numBytes = ((Number) obj).longValue();
@@ -46,19 +47,20 @@
                buf.append(formatter.format((double) numBytes)).append(" b");
            } else if (numBytes < 1024 * 1024) {
                DecimalFormat formatter = new DecimalFormat("#,##0");
                buf.append(formatter.format((double) numBytes / 1024.0)).append(" KB");
                buf.append(formatter.format(numBytes / 1024.0)).append(" KB");
            } else if (numBytes < 1024 * 1024 * 1024) {
                DecimalFormat formatter = new DecimalFormat("#,##0.0");
                buf.append(formatter.format((double) numBytes / (1024.0 * 1024.0))).append(" MB");
                buf.append(formatter.format(numBytes / (1024.0 * 1024.0))).append(" MB");
            } else {
                DecimalFormat formatter = new DecimalFormat("#,##0.0");
                buf.append(formatter.format((double) numBytes / (1024.0 * 1024.0 * 1024.0)))
                buf.append(formatter.format(numBytes / (1024.0 * 1024.0 * 1024.0)))
                        .append(" GB");
            }
        }
        return buf;
    }
    @Override
    public Object parseObject(String source, ParsePosition pos) {
        return null;
    }
src/main/java/com/gitblit/utils/ClientLogger.java
@@ -26,9 +26,9 @@
/**
 * Class to log messages to the pushing Git client. Intended to be used by the
 * Groovy Hooks.
 *
 *
 * @author John Crygier
 *
 *
 */
public class ClientLogger {
@@ -41,7 +41,7 @@
    /**
     * Sends an info/warning message to the git client.
     *
     *
     * @param message
     */
    public void info(String message) {
@@ -50,7 +50,7 @@
    /**
     * Sends an error message to the git client.
     *
     *
     * @param message
     */
    public void error(String message) {
@@ -59,7 +59,7 @@
    /**
     * Sends an error message to the git client with an exception.
     *
     *
     * @param message
     * @param t
     *            an exception
src/main/java/com/gitblit/utils/CommitCache.java
@@ -35,36 +35,36 @@
/**
 * Caches repository commits for re-use in the dashboard and activity pages.
 *
 *
 * @author James Moger
 *
 */
public class CommitCache {
    private static final CommitCache instance;
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    protected final Map<String, ObjectCache<List<RepositoryCommit>>> cache;
    protected int cacheDays = -1;
    public static CommitCache instance() {
        return instance;
    }
    static {
        instance = new CommitCache();
    }
    protected CommitCache() {
        cache = new ConcurrentHashMap<String, ObjectCache<List<RepositoryCommit>>>();
    }
    /**
     * Returns the cutoff date for the cache.  Commits after this date are cached.
     * Commits before this date are not cached.
     *
     *
     * @return
     */
    public Date getCutoffDate() {
@@ -77,28 +77,28 @@
        cal.add(Calendar.DATE, -1*cacheDays);
        return cal.getTime();
    }
    /**
     * Sets the number of days to cache.
     *
     *
     * @param days
     */
    public synchronized void setCacheDays(int days) {
        this.cacheDays = days;
        clear();
    }
    /**
     * Clears the entire commit cache.
     *
     *
     */
    public void clear() {
        cache.clear();
    }
    /**
     * Clears the commit cache for a specific repository.
     *
     *
     * @param repositoryName
     */
    public void clear(String repositoryName) {
@@ -108,10 +108,10 @@
            logger.info(MessageFormat.format("{0} commit cache cleared", repositoryName));
        }
    }
    /**
     * Clears the commit cache for a specific branch of a specific repository.
     *
     *
     * @param repositoryName
     * @param branch
     */
@@ -125,10 +125,10 @@
            }
        }
    }
    /**
     * Get all commits for the specified repository:branch that are in the cache.
     *
     *
     * @param repositoryName
     * @param repository
     * @param branch
@@ -137,12 +137,12 @@
    public List<RepositoryCommit> getCommits(String repositoryName, Repository repository, String branch) {
        return getCommits(repositoryName, repository, branch, getCutoffDate());
    }
    /**
     * Get all commits for the specified repository:branch since a specific date.
     * These commits may be retrieved from the cache if the sinceDate is after
     * the cacheCutoffDate.
     *
     *
     * @param repositoryName
     * @param repository
     * @param branch
@@ -159,13 +159,13 @@
            if (!cache.containsKey(repoKey)) {
                cache.put(repoKey, new ObjectCache<List<RepositoryCommit>>());
            }
            ObjectCache<List<RepositoryCommit>> repoCache = cache.get(repoKey);
            String branchKey = branch.toLowerCase();
            RevCommit tip = JGitUtils.getCommit(repository, branch);
            Date tipDate = JGitUtils.getCommitDate(tip);
            List<RepositoryCommit> commits;
            if (!repoCache.hasCurrent(branchKey, tipDate)) {
                commits = repoCache.getObject(branchKey);
@@ -193,7 +193,7 @@
                // update cache
                repoCache.updateObject(branchKey, tipDate, commits);
            }
            if (sinceDate.equals(cacheCutoffDate)) {
                list = commits;
            } else {
@@ -210,10 +210,10 @@
        }
        return list;
    }
    /**
     * Returns a list of commits for the specified repository branch.
     *
     * Returns a list of commits for the specified repository branch.
     *
     * @param repositoryName
     * @param repository
     * @param branch
@@ -230,10 +230,10 @@
        }
        return commits;
    }
    /**
     * Returns a list of commits for the specified repository branch since the specified commit.
     *
     * Returns a list of commits for the specified repository branch since the specified commit.
     *
     * @param repositoryName
     * @param repository
     * @param branch
@@ -250,10 +250,10 @@
        }
        return commits;
    }
    /**
     * Reduces the list of commits to those since the specified date.
     *
     *
     * @param commits
     * @param sinceDate
     * @return  a list of commits
src/main/java/com/gitblit/utils/CompressionUtils.java
@@ -43,9 +43,9 @@
/**
 * Collection of static methods for retrieving information from a repository.
 *
 *
 * @author James Moger
 *
 *
 */
public class CompressionUtils {
@@ -53,7 +53,7 @@
    /**
     * Log an error message and exception.
     *
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
@@ -77,7 +77,7 @@
    /**
     * Zips the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
@@ -137,11 +137,11 @@
        }
        return success;
    }
    /**
     * tar the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
@@ -155,11 +155,11 @@
            OutputStream os) {
        return tar(null, repository, basePath, objectId, os);
    }
    /**
     * tar.gz the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
@@ -173,11 +173,11 @@
            OutputStream os) {
        return tar(CompressorStreamFactory.GZIP, repository, basePath, objectId, os);
    }
    /**
     * tar.xz the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
@@ -191,11 +191,11 @@
            OutputStream os) {
        return tar(CompressorStreamFactory.XZ, repository, basePath, objectId, os);
    }
    /**
     * tar.bzip2 the contents of the tree at the (optionally) specified revision and
     * the (optionally) specified basepath to the supplied outputstream.
     *
     *
     * @param repository
     * @param basePath
     *            if unspecified, entire repository is assumed.
@@ -207,15 +207,15 @@
     */
    public static boolean bzip2(Repository repository, String basePath, String objectId,
            OutputStream os) {
        return tar(CompressorStreamFactory.BZIP2, repository, basePath, objectId, os);
    }
    /**
     * Compresses/archives the contents of the tree at the (optionally)
     * specified revision and the (optionally) specified basepath to the
     * supplied outputstream.
     *
     *
     * @param algorithm
     *            compression algorithm for tar (optional)
     * @param repository
@@ -233,7 +233,7 @@
        if (commit == null) {
            return false;
        }
        OutputStream cos = os;
        if (!StringUtils.isEmpty(algorithm)) {
            try {
@@ -264,7 +264,7 @@
                    continue;
                }
                tw.getObjectId(id, 0);
                ObjectLoader loader = repository.open(id);
                if (FileMode.SYMLINK == mode) {
                    TarArchiveEntry entry = new TarArchiveEntry(tw.getPathString(),TarArchiveEntry.LF_SYMLINK);
@@ -279,7 +279,7 @@
                    entry.setMode(mode.getBits());
                    entry.setModTime(modified);
                    entry.setSize(loader.getSize());
                    tos.putArchiveEntry(entry);
                    tos.putArchiveEntry(entry);
                    loader.copyTo(tos);
                    tos.closeArchiveEntry();
                }
src/main/java/com/gitblit/utils/ConnectionUtils.java
@@ -38,9 +38,9 @@
/**
 * Utility class for establishing HTTP/HTTPS connections.
 *
 *
 * @author James Moger
 *
 *
 */
public class ConnectionUtils {
@@ -61,7 +61,7 @@
        SSL_CONTEXT = context;
        HOSTNAME_VERIFIER = new DummyHostnameVerifier();
        CHARSET = "UTF-8";
        // Disable Java 7 SNI checks
        // http://stackoverflow.com/questions/7615645/ssl-handshake-alert-unrecognized-name-error-since-upgrade-to-java-1-7-0
        System.setProperty("jsse.enableSNIExtension", "false");
@@ -97,7 +97,7 @@
        }
        return conn;
    }
    // Copyright (C) 2009 The Android Open Source Project
    //
    // Licensed under the Apache License, Version 2.0 (the "License");
@@ -183,7 +183,7 @@
    /**
     * DummyTrustManager trusts all certificates.
     *
     *
     * @author James Moger
     */
    private static class DummyTrustManager implements X509TrustManager {
@@ -206,7 +206,7 @@
    /**
     * Trusts all hostnames from a certificate, including self-signed certs.
     *
     *
     * @author James Moger
     */
    private static class DummyHostnameVerifier implements HostnameVerifier {
src/main/java/com/gitblit/utils/ContainerUtils.java
@@ -27,7 +27,7 @@
/**
 * This is the support class for all container specific code.
 *
 *
 * @author jpyeron
 */
public class ContainerUtils
@@ -37,7 +37,7 @@
    /**
     * The support class for managing and evaluating the environment with
     * regards to CVE-2007-0405.
     *
     *
     * @see http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2007-0450
     * @author jpyeron
     */
@@ -80,7 +80,7 @@
         * blocked from use in certain URL s. It will emit a warning to the
         * logger if the configuration of Tomcat causes the URL processing to
         * fail on %2F.
         *
         *
         * @return true if it recognizes Tomcat, false if it does not recognize
         *         Tomcat
         */
@@ -96,7 +96,7 @@
                // mb.setBytes(test, 0, test.length);
                Method mByteChunck_setBytes = cByteChunk.getMethod("setBytes", byte[].class, int.class, int.class);
                mByteChunck_setBytes.invoke(mb, test, (int) 0, test.length);
                mByteChunck_setBytes.invoke(mb, test, 0, test.length);
                // UDecoder ud=new UDecoder();
                Class<?> cUDecoder = Class.forName("org.apache.tomcat.util.buf.UDecoder");
src/main/java/com/gitblit/utils/DeepCopier.java
@@ -57,7 +57,7 @@
     * very large objects. The current thread is used for serializing the
     * original object in order to respect any synchronization the caller may
     * have around it, and a new thread is used for deserializing the copy.
     *
     *
     */
    public static <T> T copyParallel(T original) {
        try {
@@ -88,6 +88,7 @@
            start();
        }
        @Override
        @SuppressWarnings("unchecked")
        public void run() {
src/main/java/com/gitblit/utils/DiffUtils.java
@@ -66,23 +66,23 @@
            return null;
        }
    }
    /**
     * Encapsulates the output of a diff.
     * Encapsulates the output of a diff.
     */
    public static class DiffOutput implements Serializable {
        private static final long serialVersionUID = 1L;
        public final DiffOutputType type;
        public final String content;
        public final DiffStat stat;
        DiffOutput(DiffOutputType type, String content, DiffStat stat) {
            this.type = type;
            this.content = content;
            this.stat = stat;
        }
        public PathChangeModel getPath(String path) {
            if (stat == null) {
                return null;
@@ -98,15 +98,15 @@
    public static class DiffStat implements Serializable {
        private static final long serialVersionUID = 1L;
        public final List<PathChangeModel> paths = new ArrayList<PathChangeModel>();
        private final String commitId;
        public DiffStat(String commitId) {
            this.commitId = commitId;
        }
        public PathChangeModel addPath(DiffEntry entry) {
            PathChangeModel pcm = PathChangeModel.from(entry, commitId);
            paths.add(pcm);
@@ -128,7 +128,7 @@
            }
            return val;
        }
        public PathChangeModel getPath(String path) {
            PathChangeModel stat = null;
            for (PathChangeModel p : paths) {
@@ -138,7 +138,7 @@
                }
            }
            return stat;
        }
        }
        @Override
        public String toString() {
@@ -150,15 +150,15 @@
            return sb.toString();
        }
    }
    public static class NormalizedDiffStat implements Serializable {
        private static final long serialVersionUID = 1L;
        public final int insertions;
        public final int deletions;
        public final int blanks;
        NormalizedDiffStat(int insertions, int deletions, int blanks) {
            this.insertions = insertions;
            this.deletions = deletions;
@@ -282,7 +282,7 @@
        } catch (Throwable t) {
            LOGGER.error("failed to generate commit diff!", t);
        }
        return new DiffOutput(outputType, diff, stat);
    }
@@ -442,10 +442,10 @@
        }
        return lines;
    }
    /**
     * Normalizes a diffstat to an N-segment display.
     *
     *
     * @params segments
     * @param insertions
     * @param deletions
@@ -482,7 +482,7 @@
            sd = segments - si;
            sb = 0;
        }
        return new NormalizedDiffStat(si, sd, sb);
    }
}
src/main/java/com/gitblit/utils/FederationUtils.java
@@ -44,9 +44,9 @@
/**
 * Utility methods for federation functions.
 *
 *
 * @author James Moger
 *
 *
 */
public class FederationUtils {
@@ -66,7 +66,7 @@
    /**
     * Returns an url to this servlet for the specified parameters.
     *
     *
     * @param sourceURL
     *            the url of the source gitblit instance
     * @param token
@@ -79,7 +79,7 @@
    }
    /**
     *
     *
     * @param remoteURL
     *            the url of the remote gitblit instance
     * @param tokenType
@@ -109,7 +109,7 @@
    /**
     * Returns the list of federated gitblit instances that this instance will
     * try to pull.
     *
     *
     * @return list of registered gitblit instances
     */
    public static List<FederationModel> getFederationRegistrations(IStoredSettings settings) {
@@ -194,7 +194,7 @@
     * sent by an pulling Gitblit instance to an origin Gitblit instance as part
     * of the proposal process. This is to ensure that the pulling Gitblit
     * instance has an IP route to the origin instance.
     *
     *
     * @param remoteUrl
     *            the remote Gitblit instance to send a federation proposal to
     * @param proposal
@@ -210,7 +210,7 @@
    /**
     * Sends a federation proposal to the Gitblit instance at remoteUrl
     *
     *
     * @param remoteUrl
     *            the remote Gitblit instance to send a federation proposal to
     * @param proposal
@@ -246,7 +246,7 @@
    /**
     * Retrieves a map of the repositories at the remote gitblit instance keyed
     * by the repository clone url.
     *
     *
     * @param registration
     * @param checkExclusions
     *            should returned repositories remove registration exclusions
@@ -272,7 +272,7 @@
    /**
     * Tries to pull the gitblit user accounts from the remote gitblit instance.
     *
     *
     * @param registration
     * @return a collection of UserModel objects
     * @throws Exception
@@ -287,7 +287,7 @@
    /**
     * Tries to pull the gitblit team definitions from the remote gitblit
     * instance.
     *
     *
     * @param registration
     * @return a collection of TeamModel objects
     * @throws Exception
@@ -302,7 +302,7 @@
    /**
     * Tries to pull the gitblit server settings from the remote gitblit
     * instance.
     *
     *
     * @param registration
     * @return a map of the remote gitblit settings
     * @throws Exception
@@ -315,7 +315,7 @@
    /**
     * Tries to pull the referenced scripts from the remote gitblit instance.
     *
     *
     * @param registration
     * @return a map of the remote gitblit scripts by script name
     * @throws Exception
@@ -328,7 +328,7 @@
    /**
     * Send an status acknowledgment to the remote Gitblit server.
     *
     *
     * @param identification
     *            identification of this pulling instance
     * @param registration
src/main/java/com/gitblit/utils/FileUtils.java
@@ -29,12 +29,12 @@
/**
 * Common file utilities.
 *
 *
 * @author James Moger
 *
 *
 */
public class FileUtils {
    /** 1024 (number of bytes in one kilobyte) */
    public static final int KB = 1024;
@@ -47,7 +47,7 @@
    /**
     * Returns an int from a string representation of a file size.
     * e.g. 50m = 50 megabytes
     *
     *
     * @param aString
     * @param defaultValue
     * @return an int value or the defaultValue if aString can not be parsed
@@ -55,24 +55,24 @@
    public static int convertSizeToInt(String aString, int defaultValue) {
        return (int) convertSizeToLong(aString, defaultValue);
    }
    /**
     * Returns a long from a string representation of a file size.
     * e.g. 50m = 50 megabytes
     *
     *
     * @param aString
     * @param defaultValue
     * @return a long value or the defaultValue if aString can not be parsed
     */
    public static long convertSizeToLong(String aString, long defaultValue) {
        // trim string and remove all spaces
        // trim string and remove all spaces
        aString = aString.toLowerCase().trim();
        StringBuilder sb = new StringBuilder();
        for (String a : aString.split(" ")) {
            sb.append(a);
        }
        aString = sb.toString();
        // identify value and unit
        int idx = 0;
        int len = aString.length();
@@ -99,10 +99,10 @@
        }
        return defaultValue;
    }
    /**
     * Returns the byte [] content of the specified file.
     *
     *
     * @param file
     * @return the byte content of the file
     */
@@ -130,7 +130,7 @@
    /**
     * Returns the string content of the specified file.
     *
     *
     * @param file
     * @param lineEnding
     * @return the string content of the file
@@ -166,7 +166,7 @@
    /**
     * Writes the string content to the file.
     *
     *
     * @param file
     * @param content
     */
@@ -195,14 +195,14 @@
    /**
     * Recursively traverses a folder and its subfolders to calculate the total
     * size in bytes.
     *
     *
     * @param directory
     * @return folder size in bytes
     */
    public static long folderSize(File directory) {
        if (directory == null || !directory.exists()) {
            return -1;
        }
        }
        if (directory.isDirectory()) {
            long length = 0;
            for (File file : directory.listFiles()) {
@@ -241,7 +241,7 @@
    /**
     * Copies a file or folder (recursively) to a destination folder.
     *
     *
     * @param destinationFolder
     * @param filesOrFolders
     * @return
@@ -281,11 +281,11 @@
            }
        }
    }
    /**
     * Determine the relative path between two files.  Takes into account
     * canonical paths, if possible.
     *
     *
     * @param basePath
     * @param path
     * @return a relative path from basePath to path
@@ -309,11 +309,11 @@
        // no relative relationship
        return null;
    }
    /**
     * Returns the exact path for a file. This path will be the canonical path
     * unless an exception is thrown in which case it will be the absolute path.
     *
     *
     * @param path
     * @return the exact file
     */
@@ -327,7 +327,7 @@
    public static File resolveParameter(String parameter, File aFolder, String path) {
        if (aFolder == null) {
            // strip any parameter reference
            // strip any parameter reference
            path = path.replace(parameter, "").trim();
            if (path.length() > 0 && path.charAt(0) == '/') {
                // strip leading /
src/main/java/com/gitblit/utils/GitBlitDiffFormatter.java
@@ -34,9 +34,9 @@
/**
 * 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 {
@@ -47,22 +47,22 @@
    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
@@ -88,7 +88,7 @@
        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);
@@ -127,7 +127,7 @@
            throws IOException {
        // update entry diffstat
        currentPath.update(prefix);
        // output diff
        os.write("<tr>".getBytes());
        switch (prefix) {
@@ -162,7 +162,7 @@
    /**
     * Workaround function for complex private methods in DiffFormatter. This
     * sets the html for the diff headers.
     *
     *
     * @return
     */
    public String getHtml() {
@@ -191,10 +191,10 @@
                } else {
                    // use a
                    line = line.substring("diff --git ".length()).trim();
                    line = line.substring(line.startsWith("\"a/") ? 3 : 2);
                    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);
                }
@@ -205,7 +205,7 @@
                    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>");
@@ -229,7 +229,7 @@
        sb.append("</table></div>");
        return sb.toString();
    }
    public DiffStat getDiffStat() {
        return diffStat;
    }
src/main/java/com/gitblit/utils/HttpUtils.java
@@ -30,15 +30,15 @@
/**
 * Collection of utility methods for http requests.
 *
 *
 * @author James Moger
 *
 *
 */
public class HttpUtils {
    /**
     * Returns the Gitblit URL based on the request.
     *
     *
     * @param request
     * @return the host url
     */
@@ -59,7 +59,7 @@
            } catch (Throwable t) {
            }
        }
        // try to use reverse-proxy server's scheme
        String forwardedScheme = request.getHeader("X-Forwarded-Proto");
        if (StringUtils.isEmpty(forwardedScheme)) {
@@ -68,7 +68,7 @@
        if (!StringUtils.isEmpty(forwardedScheme)) {
            // reverse-proxy server has supplied the original scheme
            scheme = forwardedScheme;
            if ("https".equals(scheme) && port == 80) {
                // proxy server is https, inside server is 80
                // this is likely because the proxy server has not supplied
@@ -77,7 +77,7 @@
                port = 443;
            }
        }
        String context = request.getContextPath();
        String forwardedContext = request.getHeader("X-Forwarded-Context");
        if (forwardedContext != null) {
@@ -86,12 +86,12 @@
        if (!StringUtils.isEmpty(forwardedContext)) {
            context = forwardedContext;
        }
        // trim any trailing slash
        if (context.length() > 0 && context.charAt(context.length() - 1) == '/') {
            context = context.substring(1);
        }
        StringBuilder sb = new StringBuilder();
        sb.append(scheme);
        sb.append("://");
@@ -103,11 +103,11 @@
        sb.append(context);
        return sb.toString();
    }
    /**
     * Returns a user model object built from attributes in the SSL certificate.
     * This model is not retrieved from the user service.
     *
     *
     * @param httpRequest
     * @param checkValidity ensure certificate can be used now
     * @param usernameOIDs if unspecified, CN is used as the username
@@ -136,7 +136,7 @@
        }
        return null;
    }
    /**
     * Creates a UserModel from a certificate
     * @param cert
@@ -145,16 +145,16 @@
     */
    public static UserModel getUserModelFromCertificate(X509Certificate cert, String... usernameOIDs) {
        X509Metadata metadata = X509Utils.getMetadata(cert);
        UserModel user = new UserModel(metadata.commonName);
        user.emailAddress = metadata.emailAddress;
        user.isAuthenticated = false;
        if (usernameOIDs == null || usernameOIDs.length == 0) {
            // use default usename<->CN mapping
            usernameOIDs = new String [] { "CN" };
        }
        // determine username from OID fingerprint
        StringBuilder an = new StringBuilder();
        for (String oid : usernameOIDs) {
@@ -163,10 +163,10 @@
                an.append(val).append(' ');
            }
        }
        user.username = an.toString().trim();
        user.username = an.toString().trim();
        return user;
    }
    public static X509Metadata getCertificateMetadata(HttpServletRequest httpRequest) {
        if (httpRequest.getAttribute("javax.servlet.request.X509Certificate") != null) {
            X509Certificate[] certChain = (X509Certificate[]) httpRequest
@@ -178,7 +178,7 @@
        }
        return null;
    }
    public static boolean isIpAddress(String address) {
        if (StringUtils.isEmpty(address)) {
            return false;
src/main/java/com/gitblit/utils/JGitUtils.java
@@ -92,9 +92,9 @@
/**
 * Collection of static methods for retrieving information from a repository.
 *
 *
 * @author James Moger
 *
 *
 */
public class JGitUtils {
@@ -102,7 +102,7 @@
    /**
     * Log an error message and exception.
     *
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
@@ -126,7 +126,7 @@
    /**
     * Returns the displayable name of the person in the form "Real Name <email
     * address>".  If the email address is empty, just "Real Name" is returned.
     *
     *
     * @param person
     * @return "Real Name <email address>" or "Real Name"
     */
@@ -155,7 +155,7 @@
     * Clone or Fetch a repository. If the local repository does not exist,
     * clone is called. If the repository does exist, fetch is called. By
     * default the clone/fetch retrieves the remote heads, tags, and notes.
     *
     *
     * @param repositoriesFolder
     * @param name
     * @param fromUrl
@@ -171,7 +171,7 @@
     * Clone or Fetch a repository. If the local repository does not exist,
     * clone is called. If the repository does exist, fetch is called. By
     * default the clone/fetch retrieves the remote heads, tags, and notes.
     *
     *
     * @param repositoriesFolder
     * @param name
     * @param fromUrl
@@ -212,7 +212,7 @@
                clone.setCredentialsProvider(credentialsProvider);
            }
            Repository repository = clone.call().getRepository();
            // Now we have to fetch because CloneCommand doesn't fetch
            // refs/notes nor does it allow manual RefSpec.
            result.createdRepository = true;
@@ -225,7 +225,7 @@
    /**
     * Fetch updates from the remote repository. If refSpecs is unspecifed,
     * remote heads, tags, and notes are retrieved.
     *
     *
     * @param credentialsProvider
     * @param repository
     * @param refSpecs
@@ -254,7 +254,7 @@
    /**
     * Creates a bare repository.
     *
     *
     * @param repositoriesFolder
     * @param name
     * @return Repository
@@ -444,7 +444,7 @@
    /**
     * Returns a list of repository names in the specified folder.
     *
     *
     * @param repositoriesFolder
     * @param onlyBare
     *            if true, only bare repositories repositories are listed. If
@@ -478,7 +478,7 @@
    /**
     * Recursive function to find git repositories.
     *
     *
     * @param basePath
     *            basePath is stripped from the repository name as repositories
     *            are relative to this path
@@ -501,7 +501,7 @@
        if (depth == 0) {
            return list;
        }
        int nextDepth = (depth == -1) ? -1 : depth - 1;
        for (File file : searchFolder.listFiles()) {
            if (file.isDirectory()) {
@@ -546,7 +546,7 @@
    /**
     * Returns the first commit on a branch. If the repository does not exist or
     * is empty, null is returned.
     *
     *
     * @param repository
     * @param branch
     *            if unspecified, HEAD is assumed.
@@ -582,7 +582,7 @@
     * Returns the date of the first commit on a branch. If the repository does
     * not exist, Date(0) is returned. If the repository does exist bit is
     * empty, the last modified date of the repository folder is returned.
     *
     *
     * @param repository
     * @param branch
     *            if unspecified, HEAD is assumed.
@@ -603,7 +603,7 @@
    /**
     * Determine if a repository has any commits. This is determined by checking
     * the for loose and packed objects.
     *
     *
     * @param repository
     * @return true if the repository has commits
     */
@@ -614,18 +614,18 @@
        }
        return false;
    }
    /**
     * Encapsulates the result of cloning or pulling from a repository.
     */
    public static class LastChange {
        public Date when;
        public String who;
        LastChange() {
            when = new Date(0);
            when = new Date(0);
        }
        LastChange(long lastModified) {
            this.when = new Date(lastModified);
        }
@@ -635,7 +635,7 @@
     * Returns the date and author of the most recent commit on a branch. If the
     * repository does not exist Date(0) is returned. If it does exist but is
     * empty, the last modified date of the repository folder is returned.
     *
     *
     * @param repository
     * @return a LastChange object
     */
@@ -652,7 +652,7 @@
        List<RefModel> branchModels = getLocalBranches(repository, true, -1);
        if (branchModels.size() > 0) {
            // find most recent branch update
            LastChange lastChange = new LastChange();
            LastChange lastChange = new LastChange();
            for (RefModel branchModel : branchModels) {
                if (branchModel.getDate().after(lastChange.when)) {
                    lastChange.when = branchModel.getDate();
@@ -661,14 +661,14 @@
            }
            return lastChange;
        }
        // default to the repository folder modification date
        return new LastChange(repository.getDirectory().lastModified());
    }
    /**
     * Retrieves a Java Date from a Git commit.
     *
     *
     * @param commit
     * @return date of the commit or Date(0) if the commit is null
     */
@@ -681,7 +681,7 @@
    /**
     * Retrieves a Java Date from a Git commit.
     *
     *
     * @param commit
     * @return date of the commit or Date(0) if the commit is null
     */
@@ -695,7 +695,7 @@
    /**
     * Returns the specified commit from the repository. If the repository does
     * not exist or is empty, null is returned.
     *
     *
     * @param repository
     * @param objectId
     *            if unspecified, HEAD is assumed.
@@ -726,7 +726,7 @@
    /**
     * Retrieves the raw byte content of a file in the specified tree.
     *
     *
     * @param repository
     * @param tree
     *            if null, the RevTree from HEAD is assumed.
@@ -782,7 +782,7 @@
    /**
     * Returns the UTF-8 string content of a file in the specified tree.
     *
     *
     * @param repository
     * @param tree
     *            if null, the RevTree from HEAD is assumed.
@@ -800,7 +800,7 @@
    /**
     * Gets the raw byte content of the specified blob object.
     *
     *
     * @param repository
     * @param objectId
     * @return byte [] blob content
@@ -831,7 +831,7 @@
    /**
     * Gets the UTF-8 string content of the blob specified by objectId.
     *
     *
     * @param repository
     * @param objectId
     * @param charsets optional
@@ -849,7 +849,7 @@
     * 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.
     *
     *
     * @param repository
     * @param path
     *            if unspecified, root folder is assumed.
@@ -900,11 +900,11 @@
        Collections.sort(list);
        return list;
    }
    /**
     * Returns the list of files changed in a specified commit. If the
     * repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param commit
     *            if null, HEAD is assumed.
@@ -917,7 +917,7 @@
    /**
     * Returns the list of files changed in a specified commit. If the
     * repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param commit
     *            if null, HEAD is assumed.
@@ -958,7 +958,7 @@
                for (DiffEntry diff : diffs) {
                    // create the path change model
                    PathChangeModel pcm = PathChangeModel.from(diff, commit.getName());
                    if (calculateDiffStat) {
                        // update file diffstats
                        df.format(diff);
@@ -982,7 +982,7 @@
    /**
     * Returns the list of files changed in a specified commit. If the
     * repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param startCommit
     *            earliest commit
@@ -1005,7 +1005,7 @@
            for (DiffEntry diff : diffEntries) {
                PathChangeModel pcm = PathChangeModel.from(diff,  null);
                list.add(pcm);
            }
            }
            Collections.sort(list);
        } catch (Throwable t) {
            error(t, repository, "{0} failed to determine files in range {1}..{2}!", startCommit, endCommit);
@@ -1016,7 +1016,7 @@
     * Returns the list of files in the repository on the default branch that
     * match one of the specified extensions. This is a CASE-SENSITIVE search.
     * If the repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param extensions
     * @return list of files in repository with a matching extension
@@ -1029,7 +1029,7 @@
     * Returns the list of files in the repository in the specified commit that
     * match one of the specified extensions. This is a CASE-SENSITIVE search.
     * If the repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param extensions
     * @param objectId
@@ -1078,7 +1078,7 @@
    /**
     * Returns a path model of the current file in the treewalk.
     *
     *
     * @param tw
     * @param basePath
     * @param commit
@@ -1106,7 +1106,7 @@
    /**
     * Returns a permissions representation of the mode bits.
     *
     *
     * @param mode
     * @return string representation of the mode bits
     */
@@ -1128,7 +1128,7 @@
    /**
     * Returns a list of commits since the minimum date starting from the
     * specified object id.
     *
     *
     * @param repository
     * @param objectId
     *            if unspecified, HEAD is assumed.
@@ -1166,7 +1166,7 @@
    /**
     * Returns a list of commits starting from HEAD and working backwards.
     *
     *
     * @param repository
     * @param maxCount
     *            if < 0, all commits for the repository are returned.
@@ -1181,7 +1181,7 @@
     * offset and maxCount for paging. This is similar to LIMIT n OFFSET p in
     * SQL. If the repository does not exist or is empty, an empty list is
     * returned.
     *
     *
     * @param repository
     * @param objectId
     *            if unspecified, HEAD is assumed.
@@ -1200,7 +1200,7 @@
     * repository. Caller may specify ending revision with objectId. Caller may
     * specify offset and maxCount to achieve pagination of results. If the
     * repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param objectId
     *            if unspecified, HEAD is assumed.
@@ -1245,7 +1245,7 @@
            RevWalk rw = new RevWalk(repository);
            rw.markStart(rw.parseCommit(endRange));
            if (startRange != null) {
                rw.markUninteresting(rw.parseCommit(startRange));
                rw.markUninteresting(rw.parseCommit(startRange));
            }
            if (!StringUtils.isEmpty(path)) {
                TreeFilter filter = AndTreeFilter.create(
@@ -1284,7 +1284,7 @@
     * Returns a list of commits for the repository within the range specified
     * by startRangeId and endRangeId. If the repository does not exist or is
     * empty, an empty list is returned.
     *
     *
     * @param repository
     * @param startRangeId
     *            the first commit (not included in results)
@@ -1329,7 +1329,7 @@
     * Search results require a specified SearchType of AUTHOR, COMMITTER, or
     * COMMIT. Results may be paginated using offset and maxCount. If the
     * repository does not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param objectId
     *            if unspecified, HEAD is assumed.
@@ -1429,7 +1429,7 @@
     * Returns the default branch to use for a repository. Normally returns
     * whatever branch HEAD points to, but if HEAD points to nothing it returns
     * the most recently updated branch.
     *
     *
     * @param repository
     * @return the objectid of a branch
     * @throws Exception
@@ -1492,7 +1492,7 @@
        }
        return target;
    }
    /**
     * Sets the symbolic ref HEAD to the specified target ref. The
     * HEAD will be detached if the target ref is not a branch.
@@ -1519,7 +1519,7 @@
            case FORCED:
            case NO_CHANGE:
            case FAST_FORWARD:
                return true;
                return true;
            default:
                LOGGER.error(MessageFormat.format("{0} HEAD update to {1} returned result {2}",
                        repository.getDirectory().getAbsolutePath(), targetRef, result));
@@ -1529,7 +1529,7 @@
        }
        return false;
    }
    /**
     * Sets the local branch ref to point to the specified commit id.
     *
@@ -1554,7 +1554,7 @@
            case FORCED:
            case NO_CHANGE:
            case FAST_FORWARD:
                return true;
                return true;
            default:
                LOGGER.error(MessageFormat.format("{0} {1} update to {2} returned result {3}",
                        repository.getDirectory().getAbsolutePath(), branchName, commitId, result));
@@ -1564,10 +1564,10 @@
        }
        return false;
    }
    /**
     * Deletes the specified branch ref.
     *
     *
     * @param repository
     * @param branch
     * @return true if successful
@@ -1587,7 +1587,7 @@
            case FORCED:
            case NO_CHANGE:
            case FAST_FORWARD:
                return true;
                return true;
            default:
                LOGGER.error(MessageFormat.format("{0} failed to delete to {1} returned result {2}",
                        repository.getDirectory().getAbsolutePath(), branchName, result));
@@ -1597,7 +1597,7 @@
        }
        return false;
    }
    /**
     * Get the full branch and tag ref names for any potential HEAD targets.
     *
@@ -1618,17 +1618,17 @@
    /**
     * Returns all refs grouped by their associated object id.
     *
     *
     * @param repository
     * @return all refs grouped by their referenced object id
     */
    public static Map<ObjectId, List<RefModel>> getAllRefs(Repository repository) {
        return getAllRefs(repository, true);
    }
    /**
     * Returns all refs grouped by their associated object id.
     *
     *
     * @param repository
     * @param includeRemoteRefs
     * @return all refs grouped by their referenced object id
@@ -1652,7 +1652,7 @@
    /**
     * Returns the list of tags in the repository. If repository does not exist
     * or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param fullName
     *            if true, /refs/tags/yadayadayada is returned. If false,
@@ -1668,7 +1668,7 @@
    /**
     * Returns the list of local branches in the repository. If repository does
     * not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param fullName
     *            if true, /refs/heads/yadayadayada is returned. If false,
@@ -1685,7 +1685,7 @@
    /**
     * Returns the list of remote branches in the repository. If repository does
     * not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param fullName
     *            if true, /refs/remotes/yadayadayada is returned. If false,
@@ -1702,7 +1702,7 @@
    /**
     * Returns the list of note branches. If repository does not exist or is
     * empty, an empty list is returned.
     *
     *
     * @param repository
     * @param fullName
     *            if true, /refs/notes/yadayadayada is returned. If false,
@@ -1715,11 +1715,11 @@
            int maxCount) {
        return getRefs(repository, Constants.R_NOTES, fullName, maxCount);
    }
    /**
     * Returns the list of refs in the specified base ref. If repository does
     * Returns the list of refs in the specified base ref. If repository does
     * not exist or is empty, an empty list is returned.
     *
     *
     * @param repository
     * @param fullName
     *            if true, /refs/yadayadayada is returned. If false,
@@ -1733,7 +1733,7 @@
    /**
     * Returns a list of references in the repository matching "refs". If the
     * repository is null or empty, an empty list is returned.
     *
     *
     * @param repository
     * @param refs
     *            if unspecified, all refs are returned
@@ -1780,7 +1780,7 @@
    /**
     * Returns a RefModel for the gh-pages branch in the repository. If the
     * branch can not be found, null is returned.
     *
     *
     * @param repository
     * @return a refmodel for the gh-pages branch or null
     */
@@ -1791,7 +1791,7 @@
    /**
     * Returns a RefModel for a specific branch name in the repository. If the
     * branch can not be found, null is returned.
     *
     *
     * @param repository
     * @return a refmodel for the branch or null
     */
@@ -1820,10 +1820,10 @@
        }
        return branch;
    }
    /**
     * Returns the list of submodules for this repository.
     *
     *
     * @param repository
     * @param commit
     * @return list of submodules
@@ -1832,10 +1832,10 @@
        RevCommit commit = getCommit(repository, commitId);
        return getSubmodules(repository, commit.getTree());
    }
    /**
     * Returns the list of submodules for this repository.
     *
     *
     * @param repository
     * @param commit
     * @return list of submodules
@@ -1858,11 +1858,11 @@
        }
        return list;
    }
    /**
     * Returns the submodule definition for the specified path at the specified
     * commit.  If no module is defined for the path, null is returned.
     *
     *
     * @param repository
     * @param commit
     * @param path
@@ -1876,7 +1876,7 @@
        }
        return null;
    }
    public static String getSubmoduleCommitId(Repository repository, String path, RevCommit commit) {
        String commitId = null;
        RevWalk rw = new RevWalk(repository);
@@ -1907,7 +1907,7 @@
     * Returns the list of notes entered about the commit from the refs/notes
     * namespace. If the repository does not exist or is empty, an empty list is
     * returned.
     *
     *
     * @param repository
     * @param commit
     * @return list of notes
@@ -1931,7 +1931,7 @@
                list.add(gitNote);
                continue;
            }
            // folder structure
            StringBuilder sb = new StringBuilder(commit.getName());
            sb.insert(2, '/');
@@ -1951,7 +1951,7 @@
    /**
     * this method creates an incremental revision number as a tag according to
     * the amount of already existing tags, which start with a defined prefix.
     *
     *
     * @param repository
     * @param objectId
     * @param tagger
@@ -1985,7 +1985,7 @@
    /**
     * creates a tag in a repository
     *
     *
     * @param repository
     * @param objectId, the ref the tag points towards
     * @param tagger, the person tagging the object
@@ -1994,7 +1994,7 @@
     * @return boolean, true if operation was successful, otherwise false
     */
    public static boolean createTag(Repository repository, String objectId, PersonIdent tagger, String tag, String message) {
        try {
        try {
            Git gitClient = Git.open(repository.getDirectory());
            TagCommand tagCommand = gitClient.tag();
            tagCommand.setTagger(tagger);
@@ -2004,17 +2004,17 @@
                tagCommand.setObjectId(revObj);
            }
            tagCommand.setName(tag);
            Ref call = tagCommand.call();
            Ref call = tagCommand.call();
            return call != null ? true : false;
        } catch (Exception e) {
            error(e, repository, "Failed to create tag {1} in repository {0}", objectId, tag);
        }
        return false;
    }
    /**
     * Create an orphaned branch in a repository.
     *
     *
     * @param repository
     * @param branchName
     * @param author
@@ -2082,10 +2082,10 @@
        }
        return success;
    }
    /**
     * Reads the sparkleshare id, if present, from the repository.
     *
     *
     * @param repository
     * @return an id or null
     */
src/main/java/com/gitblit/utils/JnaUtils.java
@@ -15,9 +15,6 @@
 */
package com.gitblit.utils;
import com.sun.jna.Library;
import com.sun.jna.Native;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@@ -28,6 +25,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jna.Library;
import com.sun.jna.Native;
/**
 * Collection of static methods to access native OS library functionality.
 *
src/main/java/com/gitblit/utils/JsonUtils.java
@@ -54,9 +54,9 @@
/**
 * Utility methods for json calls to a Gitblit server.
 *
 *
 * @author James Moger
 *
 *
 */
public class JsonUtils {
@@ -68,7 +68,7 @@
    /**
     * Creates JSON from the specified object.
     *
     *
     * @param o
     * @return json
     */
@@ -79,7 +79,7 @@
    /**
     * Convert a json string to an object of the specified type.
     *
     *
     * @param json
     * @param clazz
     * @return an object
@@ -90,7 +90,7 @@
    /**
     * Convert a json string to an object of the specified type.
     *
     *
     * @param json
     * @param clazz
     * @return an object
@@ -101,7 +101,7 @@
    /**
     * Reads a gson object from the specified url.
     *
     *
     * @param url
     * @param type
     * @return the deserialized object
@@ -114,7 +114,7 @@
    /**
     * Reads a gson object from the specified url.
     *
     *
     * @param url
     * @param type
     * @return the deserialized object
@@ -127,7 +127,7 @@
    /**
     * Reads a gson object from the specified url.
     *
     *
     * @param url
     * @param type
     * @param username
@@ -146,7 +146,7 @@
    /**
     * Reads a gson object from the specified url.
     *
     *
     * @param url
     * @param clazz
     * @param username
@@ -165,7 +165,7 @@
    /**
     * Retrieves a JSON message.
     *
     *
     * @param url
     * @return the JSON message as a string
     * @throws {@link IOException}
@@ -205,7 +205,7 @@
    /**
     * Sends a JSON message.
     *
     *
     * @param url
     *            the url to write to
     * @param json
@@ -219,7 +219,7 @@
    /**
     * Sends a JSON message.
     *
     *
     * @param url
     *            the url to write to
     * @param json
@@ -296,7 +296,7 @@
                JsonDeserializationContext jsonDeserializationContext) {
            try {
                synchronized (dateFormat) {
                    Date date = dateFormat.parse(jsonElement.getAsString());
                    Date date = dateFormat.parse(jsonElement.getAsString());
                    return new Date((date.getTime() / 1000) * 1000);
                }
            } catch (ParseException e) {
@@ -304,7 +304,7 @@
            }
        }
    }
    private static class AccessPermissionTypeAdapter implements JsonSerializer<AccessPermission>, JsonDeserializer<AccessPermission> {
        private AccessPermissionTypeAdapter() {
@@ -319,7 +319,7 @@
        @Override
        public synchronized AccessPermission deserialize(JsonElement jsonElement, Type type,
                JsonDeserializationContext jsonDeserializationContext) {
            return AccessPermission.fromCode(jsonElement.getAsString());
            return AccessPermission.fromCode(jsonElement.getAsString());
        }
    }
@@ -334,10 +334,12 @@
            this.fieldName = fqfn.substring(fqfn.lastIndexOf(".") + 1);
        }
        @Override
        public boolean shouldSkipClass(Class<?> arg0) {
            return false;
        }
        @Override
        public boolean shouldSkipField(FieldAttributes f) {
            return (f.getDeclaringClass() == c && f.getName().equals(fieldName));
        }
src/main/java/com/gitblit/utils/MarkdownUtils.java
@@ -26,15 +26,15 @@
/**
 * Utility methods for transforming raw markdown text to html.
 *
 *
 * @author James Moger
 *
 *
 */
public class MarkdownUtils {
    /**
     * Returns the html version of the markdown source text.
     *
     *
     * @param markdown
     * @return html version of markdown text
     * @throws java.text.ParseException
@@ -55,7 +55,7 @@
    /**
     * Returns the html version of the markdown source reader. The reader is
     * closed regardless of success or failure.
     *
     *
     * @param markdownReader
     * @return html version of the markdown text
     * @throws java.text.ParseException
src/main/java/com/gitblit/utils/MetricUtils.java
@@ -39,9 +39,9 @@
/**
 * Utility class for collecting metrics on a branch, tag, or other ref within
 * the repository.
 *
 *
 * @author James Moger
 *
 *
 */
public class MetricUtils {
@@ -49,7 +49,7 @@
    /**
     * Log an error message and exception.
     *
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
@@ -74,12 +74,12 @@
     * Returns the list of metrics for the specified commit reference, branch,
     * or tag within the repository. If includeTotal is true, the total of all
     * the metrics will be included as the first element in the returned list.
     *
     *
     * If the dateformat is unspecified an attempt is made to determine an
     * appropriate date format by determining the time difference between the
     * first commit on the branch and the most recent commit. This assumes that
     * the commits are linear.
     *
     *
     * @param repository
     * @param objectId
     *            if null or empty, HEAD is assumed.
@@ -172,7 +172,7 @@
    /**
     * Returns a list of author metrics for the specified repository.
     *
     *
     * @param repository
     * @param objectId
     *            if null or empty, HEAD is assumed.
src/main/java/com/gitblit/utils/ModelUtils.java
@@ -38,7 +38,7 @@
            userRepoPrefix = Constants.DEFAULT_USER_REPOSITORY_PREFIX;
            return;
        }
        String newPrefix = prefix.replace('\\', '/');
        if (prefix.charAt(0) == '/') {
            newPrefix = prefix.substring(1);
src/main/java/com/gitblit/utils/ObjectCache.java
@@ -25,9 +25,9 @@
 * milliseconds and in fast, concurrent systems this cache is too simplistic.
 * However, for the cases where its being used in Gitblit this cache technique
 * is just fine.
 *
 *
 * @author James Moger
 *
 *
 */
public class ObjectCache<X> implements Serializable {
@@ -91,7 +91,7 @@
        }
        return null;
    }
    public int size() {
        return cache.size();
    }
src/main/java/com/gitblit/utils/PatchFormatter.java
@@ -32,9 +32,9 @@
/**
 * A diff formatter that outputs standard patch content.
 *
 *
 * @author James Moger
 *
 *
 */
public class PatchFormatter extends DiffFormatter {
@@ -49,6 +49,7 @@
        this.os = os;
    }
    @Override
    public void format(DiffEntry entry) throws IOException {
        currentTouple = new PatchTouple();
        changes.put(entry.getNewPath(), currentTouple);
src/main/java/com/gitblit/utils/RefLogUtils.java
@@ -66,19 +66,19 @@
/**
 * Utility class for maintaining a reflog within a git repository on an
 * orphan branch.
 *
 *
 * @author James Moger
 *
 */
public class RefLogUtils {
    private static final String GB_REFLOG = "refs/gitblit/reflog";
    private static final Logger LOGGER = LoggerFactory.getLogger(RefLogUtils.class);
    /**
     * Log an error message and exception.
     *
     *
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
@@ -98,10 +98,10 @@
        }
        LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
    }
    /**
     * Returns true if the repository has a reflog branch.
     *
     *
     * @param repository
     * @return true if the repository has a reflog branch
     */
@@ -117,7 +117,7 @@
    /**
     * Returns a RefModel for the reflog branch in the repository. If the
     * branch can not be found, null is returned.
     *
     *
     * @param repository
     * @return a refmodel for the reflog branch or null
     */
@@ -152,7 +152,7 @@
        }
        return null;
    }
    private static UserModel newUserModelFrom(PersonIdent ident) {
        String name = ident.getName();
        String username;
@@ -165,16 +165,16 @@
            displayname = name;
            username = ident.getEmailAddress();
        }
        UserModel user = new UserModel(username);
        user.displayName = displayname;
        user.emailAddress = ident.getEmailAddress();
        return user;
    }
    /**
     * Logs a ref deletion.
     *
     *
     * @param user
     * @param repository
     * @param ref
@@ -189,7 +189,7 @@
            if (reflogBranch == null) {
                return false;
            }
            List<RevCommit> log = JGitUtils.getRevLog(repository, reflogBranch.getName(), ref.getName(), 0, 1);
            if (log.isEmpty()) {
                // this ref is not in the reflog branch
@@ -202,10 +202,10 @@
        }
        return false;
    }
    /**
     * Updates the reflog with the received commands.
     *
     *
     * @param user
     * @param repository
     * @param commands
@@ -217,10 +217,10 @@
        if (reflogBranch == null) {
            JGitUtils.createOrphanBranch(repository, GB_REFLOG, null);
        }
        boolean success = false;
        String message = "push";
        try {
            ObjectId headId = repository.resolve(GB_REFLOG + "^{commit}");
            ObjectInserter odi = repository.newObjectInserter();
@@ -286,17 +286,17 @@
        }
        return success;
    }
    /**
     * Creates an in-memory index of the push log entry.
     *
     *
     * @param repo
     * @param headId
     * @param commands
     * @return an in-memory index
     * @throws IOException
     */
    private static DirCache createIndex(Repository repo, ObjectId headId,
    private static DirCache createIndex(Repository repo, ObjectId headId,
            Collection<ReceiveCommand> commands) throws IOException {
        DirCache inCoreIndex = DirCache.newInCore();
@@ -335,7 +335,7 @@
                    continue;
                }
                String content = change.toString();
                // create an index entry for this attachment
                final DirCacheEntry dcEntry = new DirCacheEntry(path);
                dcEntry.setLength(content.length());
@@ -386,7 +386,7 @@
        }
        return inCoreIndex;
    }
    public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository) {
        return getRefLog(repositoryName, repository, null, 0, -1);
    }
@@ -402,11 +402,11 @@
    public static List<RefLogEntry> getRefLog(String repositoryName, Repository repository, Date minimumDate) {
        return getRefLog(repositoryName, repository, minimumDate, 0, -1);
    }
    /**
     * Returns the list of reflog entries as they were recorded by Gitblit.
     * Each RefLogEntry may represent multiple ref updates.
     *
     *
     * @param repositoryName
     * @param repository
     * @param minimumDate
@@ -425,7 +425,7 @@
        if (maxCount == 0) {
            return list;
        }
        Map<ObjectId, List<RefModel>> allRefs = JGitUtils.getAllRefs(repository);
        List<RevCommit> pushes;
        if (minimumDate == null) {
@@ -441,8 +441,8 @@
            UserModel user = newUserModelFrom(push.getAuthorIdent());
            Date date = push.getAuthorIdent().getWhen();
            RefLogEntry log = new RefLogEntry(repositoryName, date, user);
            RefLogEntry log = new RefLogEntry(repositoryName, date, user);
            List<PathChangeModel> changedRefs = JGitUtils.getFilesInCommit(repository, push);
            if (changedRefs.isEmpty()) {
                // skip empty commits
@@ -483,7 +483,7 @@
    /**
     * Returns the list of pushes separated by ref (e.g. each ref has it's own
     * PushLogEntry object).
     *
     *
     * @param repositoryName
     * @param repository
     * @param maxCount
@@ -492,11 +492,11 @@
    public static List<RefLogEntry> getLogByRef(String repositoryName, Repository repository, int maxCount) {
        return getLogByRef(repositoryName, repository, 0, maxCount);
    }
    /**
     * Returns the list of pushes separated by ref (e.g. each ref has it's own
     * PushLogEntry object).
     *
     *
     * @param repositoryName
     * @param repository
     * @param offset
@@ -513,7 +513,7 @@
                if (!refMap.containsKey(ref)) {
                    refMap.put(ref, new ArrayList<RefLogEntry>());
                }
                // construct new ref-specific ref change entry
                RefLogEntry refChange;
                if (entry instanceof DailyLogEntry) {
@@ -528,23 +528,23 @@
                refMap.get(ref).add(refChange);
            }
        }
        // merge individual ref changes into master list
        List<RefLogEntry> mergedRefLog = new ArrayList<RefLogEntry>();
        for (List<RefLogEntry> refPush : refMap.values()) {
            mergedRefLog.addAll(refPush);
        }
        // sort ref log
        Collections.sort(mergedRefLog);
        return mergedRefLog;
    }
    /**
     * Returns the list of ref changes separated by ref (e.g. each ref has it's own
     * RefLogEntry object).
     *
     *
     * @param repositoryName
     * @param repository
     * @param minimumDate
@@ -567,16 +567,16 @@
                refMap.get(ref).add(refPush);
            }
        }
        // merge individual ref pushes into master list
        List<RefLogEntry> refPushLog = new ArrayList<RefLogEntry>();
        for (List<RefLogEntry> refPush : refMap.values()) {
            refPushLog.addAll(refPush);
        }
        // sort ref push log
        Collections.sort(refPushLog);
        return refPushLog;
    }
@@ -631,12 +631,12 @@
                    linearParent = commit.getParents()[0].getId().getName();
                    digest.updateRef(branch, ReceiveCommand.Type.UPDATE, linearParent, commit.getName());
                }
                RepositoryCommit repoCommit = digest.addCommit(commit);
                if (repoCommit != null) {
                    List<RefModel> matchedRefs = allRefs.get(commit.getId());
                    repoCommit.setRefs(matchedRefs);
                    if (!ArrayUtils.isEmpty(matchedRefs)) {
                        for (RefModel ref : matchedRefs) {
                            if (ref.getName().startsWith(Constants.R_TAGS)) {
src/main/java/com/gitblit/utils/RpcUtils.java
@@ -39,9 +39,9 @@
/**
 * Utility methods for rpc calls.
 *
 *
 * @author James Moger
 *
 *
 */
public class RpcUtils {
@@ -76,7 +76,7 @@
    }.getType();
    /**
     *
     *
     * @param remoteURL
     *            the url of the remote gitblit instance
     * @param req
@@ -88,7 +88,7 @@
    }
    /**
     *
     *
     * @param remoteURL
     *            the url of the remote gitblit instance
     * @param req
@@ -110,7 +110,7 @@
    /**
     * Returns the version of the RPC protocol on the server.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -124,7 +124,7 @@
        try {
            protocol = JsonUtils.retrieveJson(url, Integer.class, account, password);
        } catch (UnknownRequestException e) {
            // v0.7.0 (protocol 1) did not have this request type
            // v0.7.0 (protocol 1) did not have this request type
        }
        return protocol;
    }
@@ -132,7 +132,7 @@
    /**
     * Retrieves a map of the repositories at the remote gitblit instance keyed
     * by the repository clone url.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -149,7 +149,7 @@
    /**
     * Tries to pull the gitblit user accounts from the remote gitblit instance.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -167,7 +167,7 @@
    /**
     * Tries to pull the gitblit team definitions from the remote gitblit
     * instance.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -184,7 +184,7 @@
    /**
     * Create a repository on the Gitblit server.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -205,7 +205,7 @@
    /**
     * Send a revised version of the repository model to the Gitblit server.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -221,7 +221,7 @@
    /**
     * Delete a repository from the Gitblit server.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -235,17 +235,17 @@
                password);
    }
    /**
     * Clears the repository cache on the Gitblit server.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
     * @return true if the action succeeded
     * @throws IOException
     */
    public static boolean clearRepositoryCache(String serverUrl, String account,
    public static boolean clearRepositoryCache(String serverUrl, String account,
            char[] password) throws IOException {
        return doAction(RpcRequest.CLEAR_REPOSITORY_CACHE, null, null, serverUrl, account,
                password);
@@ -253,7 +253,7 @@
    /**
     * Create a user on the Gitblit server.
     *
     *
     * @param user
     * @param serverUrl
     * @param account
@@ -269,7 +269,7 @@
    /**
     * Send a revised version of the user model to the Gitblit server.
     *
     *
     * @param user
     * @param serverUrl
     * @param account
@@ -285,7 +285,7 @@
    /**
     * Deletes a user from the Gitblit server.
     *
     *
     * @param user
     * @param serverUrl
     * @param account
@@ -297,11 +297,11 @@
            char[] password) throws IOException {
        return doAction(RpcRequest.DELETE_USER, null, user, serverUrl, account, password);
    }
    /**
     * Tries to get the specified gitblit user account from the remote gitblit instance.
     * If the username is null or empty, the current user is returned.
     *
     *
     * @param username
     * @param serverUrl
     * @param account
@@ -318,7 +318,7 @@
    /**
     * Create a team on the Gitblit server.
     *
     *
     * @param team
     * @param serverUrl
     * @param account
@@ -334,7 +334,7 @@
    /**
     * Send a revised version of the team model to the Gitblit server.
     *
     *
     * @param team
     * @param serverUrl
     * @param account
@@ -350,7 +350,7 @@
    /**
     * Deletes a team from the Gitblit server.
     *
     *
     * @param team
     * @param serverUrl
     * @param account
@@ -365,7 +365,7 @@
    /**
     * Retrieves the list of users that can access the specified repository.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -379,10 +379,10 @@
        Collection<String> list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password);
        return new ArrayList<String>(list);
    }
    /**
     * Retrieves the list of user access permissions for the specified repository.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -390,7 +390,7 @@
     * @return list of User-AccessPermission tuples
     * @throws IOException
     */
    public static List<RegistrantAccessPermission> getRepositoryMemberPermissions(RepositoryModel repository,
    public static List<RegistrantAccessPermission> getRepositoryMemberPermissions(RepositoryModel repository,
            String serverUrl, String account, char [] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_MEMBER_PERMISSIONS, repository.name);
        Collection<RegistrantAccessPermission> list = JsonUtils.retrieveJson(url, REGISTRANT_PERMISSIONS_TYPE, account, password);
@@ -399,7 +399,7 @@
    /**
     * Sets the repository user access permissions
     *
     *
     * @param repository
     * @param permissions
     * @param serverUrl
@@ -414,10 +414,10 @@
        return doAction(RpcRequest.SET_REPOSITORY_MEMBER_PERMISSIONS, repository.name, permissions, serverUrl,
                account, password);
    }
    /**
     * Retrieves the list of teams that can access the specified repository.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -431,10 +431,10 @@
        Collection<String> list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password);
        return new ArrayList<String>(list);
    }
    /**
     * Retrieves the list of team access permissions for the specified repository.
     *
     *
     * @param repository
     * @param serverUrl
     * @param account
@@ -442,7 +442,7 @@
     * @return list of Team-AccessPermission tuples
     * @throws IOException
     */
    public static List<RegistrantAccessPermission> getRepositoryTeamPermissions(RepositoryModel repository,
    public static List<RegistrantAccessPermission> getRepositoryTeamPermissions(RepositoryModel repository,
            String serverUrl, String account, char [] password) throws IOException {
        String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_TEAM_PERMISSIONS, repository.name);
        Collection<RegistrantAccessPermission> list = JsonUtils.retrieveJson(url, REGISTRANT_PERMISSIONS_TYPE, account, password);
@@ -451,7 +451,7 @@
    /**
     * Sets the repository team access permissions
     *
     *
     * @param repository
     * @param permissions
     * @param serverUrl
@@ -466,11 +466,11 @@
        return doAction(RpcRequest.SET_REPOSITORY_TEAM_PERMISSIONS, repository.name, permissions, serverUrl,
                account, password);
    }
    /**
     * Retrieves the list of federation registrations. These are the list of
     * registrations that this Gitblit instance is pulling from.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -489,7 +489,7 @@
    /**
     * Retrieves the list of federation result registrations. These are the
     * results reported back to this Gitblit instance from a federation client.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -507,7 +507,7 @@
    /**
     * Retrieves the list of federation proposals.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -525,7 +525,7 @@
    /**
     * Retrieves the list of federation repository sets.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -542,7 +542,7 @@
    /**
     * Retrieves the settings of the Gitblit server.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -559,7 +559,7 @@
    /**
     * Update the settings on the Gitblit server.
     *
     *
     * @param settings
     *            the settings to update
     * @param serverUrl
@@ -576,7 +576,7 @@
    /**
     * Retrieves the server status object.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -593,7 +593,7 @@
    /**
     * Retrieves a map of local branches in the Gitblit server keyed by
     * repository.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -610,7 +610,7 @@
    /**
     * Retrieves a list of available branch feeds in the Gitblit server.
     *
     *
     * @param serverUrl
     * @param account
     * @param password
@@ -634,7 +634,7 @@
    /**
     * Do the specified administrative action on the Gitblit server.
     *
     *
     * @param request
     * @param name
     *            the name of the object (may be null)
src/main/java/com/gitblit/utils/StringUtils.java
@@ -40,9 +40,9 @@
/**
 * Utility class of string functions.
 *
 *
 * @author James Moger
 *
 *
 */
public class StringUtils {
@@ -52,7 +52,7 @@
    /**
     * Returns true if the string is null or empty.
     *
     *
     * @param value
     * @return true if string is null or empty
     */
@@ -62,7 +62,7 @@
    /**
     * Replaces carriage returns and line feeds with html line breaks.
     *
     *
     * @param string
     * @return plain text with html line breaks
     */
@@ -73,7 +73,7 @@
    /**
     * Prepare text for html presentation. Replace sensitive characters with
     * html entities.
     *
     *
     * @param inStr
     * @param changeSpace
     * @return plain text escaped for html
@@ -104,7 +104,7 @@
    /**
     * Decode html entities back into plain text characters.
     *
     *
     * @param inStr
     * @return returns plain text from html
     */
@@ -115,7 +115,7 @@
    /**
     * Encodes a url parameter by escaping troublesome characters.
     *
     *
     * @param inStr
     * @return properly escaped url
     */
@@ -137,7 +137,7 @@
    /**
     * Flatten the list of strings into a single string with a space separator.
     *
     *
     * @param values
     * @return flattened list
     */
@@ -148,7 +148,7 @@
    /**
     * Flatten the list of strings into a single string with the specified
     * separator.
     *
     *
     * @param values
     * @param separator
     * @return flattened list
@@ -169,7 +169,7 @@
     * Returns a string trimmed to a maximum length with trailing ellipses. If
     * the string length is shorter than the max, the original string is
     * returned.
     *
     *
     * @param value
     * @param max
     * @return trimmed string
@@ -184,7 +184,7 @@
    /**
     * Left pad a string with the specified character, if the string length is
     * less than the specified length.
     *
     *
     * @param input
     * @param length
     * @param pad
@@ -205,7 +205,7 @@
    /**
     * Right pad a string with the specified character, if the string length is
     * less then the specified length.
     *
     *
     * @param input
     * @param length
     * @param pad
@@ -225,7 +225,7 @@
    /**
     * Calculates the SHA1 of the string.
     *
     *
     * @param text
     * @return sha1 of the string
     */
@@ -240,7 +240,7 @@
    /**
     * Calculates the SHA1 of the byte array.
     *
     *
     * @param bytes
     * @return sha1 of the byte array
     */
@@ -257,7 +257,7 @@
    /**
     * Calculates the MD5 of the string.
     *
     *
     * @param string
     * @return md5 of the string
     */
@@ -268,10 +268,10 @@
            throw new RuntimeException(u);
        }
    }
    /**
     * Calculates the MD5 of the string.
     *
     *
     * @param string
     * @return md5 of the string
     */
@@ -289,17 +289,17 @@
    /**
     * Returns the hex representation of the byte array.
     *
     *
     * @param bytes
     * @return byte array as hex string
     */
    private static String toHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            if (((int) bytes[i] & 0xff) < 0x10) {
            if ((bytes[i] & 0xff) < 0x10) {
                sb.append('0');
            }
            sb.append(Long.toString((int) bytes[i] & 0xff, 16));
            sb.append(Long.toString(bytes[i] & 0xff, 16));
        }
        return sb.toString();
    }
@@ -307,7 +307,7 @@
    /**
     * Returns the root path of the specified path. Returns a blank string if
     * there is no root path.
     *
     *
     * @param path
     * @return root path or blank
     */
@@ -321,7 +321,7 @@
    /**
     * Returns the path remainder after subtracting the basePath from the
     * fullPath.
     *
     *
     * @param basePath
     * @param fullPath
     * @return the relative path
@@ -341,7 +341,7 @@
    /**
     * Splits the space-separated string into a list of strings.
     *
     *
     * @param value
     * @return list of strings
     */
@@ -351,7 +351,7 @@
    /**
     * Splits the string into a list of string by the specified separator.
     *
     *
     * @param value
     * @param separator
     * @return list of strings
@@ -359,7 +359,7 @@
    public static List<String> getStringsFromValue(String value, String separator) {
        List<String> strings = new ArrayList<String>();
        try {
            String[] chunks = value.split(separator + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
            String[] chunks = value.split(separator + "(?=([^\"]*\"[^\"]*\")*[^\"]*$)");
            for (String chunk : chunks) {
                chunk = chunk.trim();
                if (chunk.length() > 0) {
@@ -379,7 +379,7 @@
    /**
     * Validates that a name is composed of letters, digits, or limited other
     * characters.
     *
     *
     * @param name
     * @return the first invalid character found or null if string is acceptable
     */
@@ -402,7 +402,7 @@
    /**
     * Simple fuzzy string comparison. This is a case-insensitive check. A
     * single wildcard * value is supported.
     *
     *
     * @param value
     * @param pattern
     * @return true if the value matches the pattern
@@ -431,7 +431,7 @@
    /**
     * Compare two repository names for proper group sorting.
     *
     *
     * @param r1
     * @param r2
     * @return
@@ -459,7 +459,7 @@
    /**
     * Sort grouped repository names.
     *
     *
     * @param list
     */
    public static void sortRepositorynames(List<String> list) {
@@ -476,7 +476,7 @@
        for (char c : getMD5(value.toLowerCase()).toCharArray()) {
            cs += c;
        }
        int n = (cs % 360);
        int n = (cs % 360);
        float hue = ((float) n) / 360;
        return hsvToRgb(hue, 0.90f, 0.65f);
    }
@@ -514,10 +514,10 @@
        String bs = Integer.toHexString((int) (b * 256));
        return "#" + rs + gs + bs;
    }
    /**
     * Strips a trailing ".git" from the value.
     *
     *
     * @param value
     * @return a stripped value or the original value if .git is not found
     */
@@ -527,10 +527,10 @@
        }
        return value;
    }
    /**
     * Count the number of lines in a string.
     *
     *
     * @param value
     * @return the line count
     */
@@ -540,10 +540,10 @@
        }
        return value.split("\n").length;
    }
    /**
     * Returns the file extension of a path.
     *
     *
     * @param path
     * @return a blank string or a file extension
     */
@@ -554,13 +554,13 @@
        }
        return "";
    }
    /**
     * Replace all occurences of a substring within a string with
     * another string.
     *
     *
     * From Spring StringUtils.
     *
     *
     * @param inString String to examine
     * @param oldPattern String to replace
     * @param newPattern String to insert
@@ -582,12 +582,12 @@
        // remember to append any characters to the right of a match
        return sb.toString();
    }
    /**
     * Decodes a string by trying several charsets until one does not throw a
     * coding exception.  Last resort is to interpret as UTF-8 with illegal
     * character substitution.
     *
     *
     * @param content
     * @param charsets optional
     * @return a string
@@ -620,12 +620,12 @@
        }
        return value;
    }
    /**
     * Attempt to extract a repository name from a given url using regular
     * expressions.  If no match is made, then return whatever trails after
     * the final / character.
     *
     *
     * @param regexUrls
     * @return a repository path
     */
@@ -644,7 +644,7 @@
        }
        return url;
    }
    /**
     * Converts a string with \nnn sequences into a UTF-8 encoded string.
     * @param input
@@ -662,7 +662,7 @@
                // strip leading \ character
                String oct = m.group().substring(1);
                bytes.write(Integer.parseInt(oct, 8));
                i = m.end();
                i = m.end();
            }
            if (bytes.size() == 0) {
                // no octal matches
@@ -679,11 +679,11 @@
        }
        return input;
    }
    /**
     * Returns the first path element of a path string.  If no path separator is
     * found in the path, an empty string is returned.
     *
     * found in the path, an empty string is returned.
     *
     * @param path
     * @return the first element in the path
     */
@@ -693,10 +693,10 @@
        }
        return "";
    }
    /**
     * Returns the last path element of a path string
     *
     *
     * @param path
     * @return the last element in the path
     */
@@ -706,10 +706,10 @@
        }
        return path;
    }
    /**
     * Variation of String.matches() which disregards case issues.
     *
     *
     * @param regex
     * @param input
     * @return true if the pattern matches
@@ -719,11 +719,11 @@
        Matcher m = p.matcher(input);
        return m.matches();
    }
    /**
     * Removes new line and carriage return chars from a string.
     * If input value is null an empty string is returned.
     *
     *
     * @param input
     * @return a sanitized or empty string
     */
src/main/java/com/gitblit/utils/SyndicationUtils.java
@@ -43,15 +43,15 @@
/**
 * Utility class for RSS feeds.
 *
 *
 * @author James Moger
 *
 *
 */
public class SyndicationUtils {
    /**
     * Outputs an RSS feed of the list of entries to the outputstream.
     *
     *
     * @param hostUrl
     * @param feedLink
     * @param title
@@ -118,7 +118,7 @@
    /**
     * Reads a Gitblit RSS feed.
     *
     *
     * @param url
     *            the url of the Gitblit server
     * @param repository
@@ -153,7 +153,7 @@
    /**
     * Reads a Gitblit RSS search feed.
     *
     *
     * @param url
     *            the url of the Gitblit server
     * @param repository
@@ -195,7 +195,7 @@
    /**
     * Reads a Gitblit RSS feed.
     *
     *
     * @param url
     *            the url of the Gitblit server
     * @param parameters
src/main/java/com/gitblit/utils/TimeUtils.java
@@ -24,9 +24,9 @@
/**
 * Utility class of time functions.
 *
 *
 * @author James Moger
 *
 *
 */
public class TimeUtils {
    public static final long MIN = 1000 * 60L;
@@ -38,15 +38,15 @@
    public static final long ONEDAY = ONEHOUR * 24L;
    public static final long ONEYEAR = ONEDAY * 365L;
    private final ResourceBundle translation;
    private final TimeZone timezone;
    public TimeUtils() {
        this(null, null);
    }
    public TimeUtils(ResourceBundle translation, TimeZone timezone) {
        this.translation = translation;
        this.timezone = timezone;
@@ -54,7 +54,7 @@
    /**
     * Returns true if date is today.
     *
     *
     * @param date
     * @return true if date is today
     */
@@ -69,7 +69,7 @@
    /**
     * Returns true if date is yesterday.
     *
     *
     * @param date
     * @return true if date is yesterday
     */
@@ -87,7 +87,7 @@
    /**
     * Returns the string representation of the duration as days, months and/or
     * years.
     *
     *
     * @param days
     * @return duration as string in days, months, and/or years
     */
@@ -123,7 +123,7 @@
    /**
     * Returns the number of minutes ago between the start time and the end
     * time.
     *
     *
     * @param date
     * @param endTime
     * @param roundup
@@ -140,7 +140,7 @@
    /**
     * Return the difference in minutes between now and the date.
     *
     *
     * @param date
     * @param roundup
     * @return minutes ago
@@ -151,7 +151,7 @@
    /**
     * Return the difference in hours between now and the date.
     *
     *
     * @param date
     * @param roundup
     * @return hours ago
@@ -167,7 +167,7 @@
    /**
     * Return the difference in days between now and the date.
     *
     *
     * @param date
     * @return days ago
     */
@@ -190,7 +190,7 @@
    /**
     * Returns the string representation of the duration between now and the
     * date.
     *
     *
     * @param date
     * @return duration as a string
     */
@@ -200,7 +200,7 @@
    /**
     * Returns the CSS class for the date based on its age from Now.
     *
     *
     * @param date
     * @return the css class
     */
@@ -211,7 +211,7 @@
    /**
     * Returns the string representation of the duration OR the css class for
     * the duration.
     *
     *
     * @param date
     * @param css
     * @return the string representation of the duration OR the css class
@@ -279,7 +279,7 @@
            }
        }
    }
    public String inFuture(Date date) {
        long diff = date.getTime() - System.currentTimeMillis();
        if (diff > ONEDAY) {
@@ -295,7 +295,7 @@
            }
        }
    }
    private String translate(String key, String defaultValue) {
        String value = defaultValue;
        if (translation != null && translation.containsKey(key)) {
@@ -306,7 +306,7 @@
        }
        return value;
    }
    private String translate(int val, String key, String defaultPattern) {
        String pattern = defaultPattern;
        if (translation != null && translation.containsKey(key)) {
@@ -320,7 +320,7 @@
    /**
     * Convert a frequency string into minutes.
     *
     *
     * @param frequency
     * @return minutes
     */
src/main/java/com/gitblit/utils/X509Utils.java
@@ -89,43 +89,43 @@
/**
 * Utility class to generate X509 certificates, keystores, and truststores.
 *
 *
 * @author James Moger
 *
 *
 */
public class X509Utils {
    public static final String SERVER_KEY_STORE = "serverKeyStore.jks";
    public static final String SERVER_TRUST_STORE = "serverTrustStore.jks";
    public static final String CERTS = "certs";
    public static final String CA_KEY_STORE = "certs/caKeyStore.p12";
    public static final String CA_REVOCATION_LIST = "certs/caRevocationList.crl";
    public static final String CA_CONFIG = "certs/authority.conf";
    public static final String CA_CN = "Gitblit Certificate Authority";
    public static final String CA_ALIAS = CA_CN;
    private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
    private static final int KEY_LENGTH = 2048;
    private static final String KEY_ALGORITHM = "RSA";
    private static final String SIGNING_ALGORITHM = "SHA512withRSA";
    public static final boolean unlimitedStrength;
    private static final Logger logger = LoggerFactory.getLogger(X509Utils.class);
    static {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        // check for JCE Unlimited Strength
        int maxKeyLen = 0;
        try {
@@ -140,28 +140,28 @@
            logger.info("Using JCE Standard Encryption Policy files, encryption key lengths will be limited");
        }
    }
    public static enum RevocationReason {
        // https://en.wikipedia.org/wiki/Revocation_list
         unspecified, keyCompromise, caCompromise, affiliationChanged, superseded,
         cessationOfOperation, certificateHold, unused, removeFromCRL, privilegeWithdrawn,
         ACompromise;
         public static RevocationReason [] reasons = {
                 unspecified, keyCompromise, caCompromise,
                 affiliationChanged, superseded, cessationOfOperation,
                 unspecified, keyCompromise, caCompromise,
                 affiliationChanged, superseded, cessationOfOperation,
                 privilegeWithdrawn };
         @Override
         public String toString() {
             return name() +  " (" + ordinal() + ")";
         }
    }
    public interface X509Log {
        void log(String message);
    }
    public static class X509Metadata {
        // map for distinguished name OIDs
@@ -169,10 +169,10 @@
        // CN in distingiushed name
        public final String commonName;
        // password for store
        public final String password;
        // password hint for README in bundle
        public String passwordHint;
@@ -181,13 +181,13 @@
        // start date of generated certificate
        public Date notBefore;
        // expiraiton date of generated certificate
        public Date notAfter;
        // hostname of server for which certificate is generated
        public String serverHostname;
        // displayname of user for README in bundle
        public String userDisplayname;
@@ -213,7 +213,7 @@
            notAfter = c.getTime();
            oids = new HashMap<String, String>();
        }
        public X509Metadata clone(String commonName, String password) {
            X509Metadata clone = new X509Metadata(commonName, password);
            clone.emailAddress = emailAddress;
@@ -225,14 +225,14 @@
            clone.userDisplayname = userDisplayname;
            return clone;
        }
        public String getOID(String oid, String defaultValue) {
            if (oids.containsKey(oid)) {
                return oids.get(oid);
            }
            return defaultValue;
        }
        public void setOID(String oid, String value) {
            if (StringUtils.isEmpty(value)) {
                oids.remove(oid);
@@ -241,10 +241,10 @@
            }
        }
    }
    /**
     * Prepare all the certificates and stores necessary for a Gitblit GO server.
     *
     *
     * @param metadata
     * @param folder
     * @param x509log
@@ -252,9 +252,9 @@
    public static void prepareX509Infrastructure(X509Metadata metadata, File folder, X509Log x509log) {
        // make the specified folder, if necessary
        folder.mkdirs();
        // Gitblit CA certificate
        File caKeyStore = new File(folder, CA_KEY_STORE);
        File caKeyStore = new File(folder, CA_KEY_STORE);
        if (!caKeyStore.exists()) {
            logger.info(MessageFormat.format("Generating {0} ({1})", CA_CN, caKeyStore.getAbsolutePath()));
            X509Certificate caCert = newCertificateAuthority(metadata, caKeyStore, x509log);
@@ -262,7 +262,7 @@
        }
        // Gitblit CRL
        File caRevocationList = new File(folder, CA_REVOCATION_LIST);
        File caRevocationList = new File(folder, CA_REVOCATION_LIST);
        if (!caRevocationList.exists()) {
            logger.info(MessageFormat.format("Generating {0} CRL ({1})", CA_CN, caRevocationList.getAbsolutePath()));
            newCertificateRevocationList(caRevocationList, caKeyStore, metadata.password);
@@ -273,7 +273,7 @@
        File oldKeyStore = new File(folder, "keystore");
        if (oldKeyStore.exists()) {
            oldKeyStore.renameTo(new File(folder, SERVER_KEY_STORE));
            logger.info(MessageFormat.format("Renaming {0} to {1}", oldKeyStore.getName(), SERVER_KEY_STORE));
            logger.info(MessageFormat.format("Renaming {0} to {1}", oldKeyStore.getName(), SERVER_KEY_STORE));
        }
        // create web SSL certificate signed by CA
@@ -282,7 +282,7 @@
            logger.info(MessageFormat.format("Generating SSL certificate for {0} signed by {1} ({2})", metadata.commonName, CA_CN, serverKeyStore.getAbsolutePath()));
            PrivateKey caPrivateKey = getPrivateKey(CA_ALIAS, caKeyStore, metadata.password);
            X509Certificate caCert = getCertificate(CA_ALIAS, caKeyStore, metadata.password);
            newSSLCertificate(metadata, caPrivateKey, caCert, serverKeyStore, x509log);
            newSSLCertificate(metadata, caPrivateKey, caCert, serverKeyStore, x509log);
        }
        // server certificate trust store holds trusted public certificates
@@ -293,11 +293,11 @@
            addTrustedCertificate(CA_ALIAS, caCert, serverTrustStore, metadata.password);
        }
    }
    /**
     * Open a keystore.  Store type is determined by file extension of name. If
     * undetermined, JKS is assumed.  The keystore does not need to exist.
     *
     *
     * @param storeFile
     * @param storePassword
     * @return a KeyStore
@@ -336,10 +336,10 @@
            throw new RuntimeException("Could not open keystore " + storeFile, e);
        }
    }
    /**
     * Saves the keystore to the specified file.
     *
     *
     * @param targetStoreFile
     * @param store
     * @param password
@@ -376,17 +376,17 @@
                } catch (IOException e) {
                }
            }
            if (tmpFile.exists()) {
                tmpFile.delete();
            }
        }
    }
    }
    /**
     * Retrieves the X509 certificate with the specified alias from the certificate
     * store.
     *
     *
     * @param alias
     * @param storeFile
     * @param storePassword
@@ -401,11 +401,11 @@
            throw new RuntimeException(e);
        }
    }
    /**
     * Retrieves the private key for the specified alias from the certificate
     * store.
     *
     *
     * @param alias
     * @param storeFile
     * @param storePassword
@@ -425,7 +425,7 @@
     * Saves the certificate to the file system.  If the destination filename
     * ends with the pem extension, the certificate is written in the PEM format,
     * otherwise the certificate is written in the DER format.
     *
     *
     * @param cert
     * @param targetFile
     */
@@ -443,7 +443,7 @@
                try {
                    pemWriter = new PEMWriter(new FileWriter(tmpFile));
                    pemWriter.writeObject(cert);
                    pemWriter.flush();
                    pemWriter.flush();
                } finally {
                    if (pemWriter != null) {
                        pemWriter.close();
@@ -462,9 +462,9 @@
                    }
                }
            }
            // rename tmp file to target
            if (targetFile.exists()) {
            if (targetFile.exists()) {
                targetFile.delete();
            }
            tmpFile.renameTo(targetFile);
@@ -475,10 +475,10 @@
            throw new RuntimeException("Failed to save certificate " + cert.getSubjectX500Principal().getName(), e);
        }
    }
    /**
     * Generate a new keypair.
     *
     *
     * @return a keypair
     * @throws Exception
     */
@@ -487,10 +487,10 @@
        kpGen.initialize(KEY_LENGTH, new SecureRandom());
        return kpGen.generateKeyPair();
    }
    /**
     * Builds a distinguished name from the X509Metadata.
     *
     *
     * @return a DN
     */
    private static X500Name buildDistinguishedName(X509Metadata metadata) {
@@ -501,14 +501,14 @@
        setOID(dnBuilder, metadata, "O", Constants.NAME);
        setOID(dnBuilder, metadata, "OU", Constants.NAME);
        setOID(dnBuilder, metadata, "E", metadata.emailAddress);
        setOID(dnBuilder, metadata, "CN", metadata.commonName);
        setOID(dnBuilder, metadata, "CN", metadata.commonName);
        X500Name dn = dnBuilder.build();
        return dn;
    }
    private static void setOID(X500NameBuilder dnBuilder, X509Metadata metadata,
            String oid, String defaultValue) {
        String value = null;
        if (metadata.oids != null && metadata.oids.containsKey(oid)) {
            value = metadata.oids.get(oid);
@@ -516,7 +516,7 @@
        if (StringUtils.isEmpty(value)) {
            value = defaultValue;
        }
        if (!StringUtils.isEmpty(value)) {
            try {
                Field field = BCStyle.class.getField(oid);
@@ -531,7 +531,7 @@
    /**
     * Creates a new SSL certificate signed by the CA private key and stored in
     * keyStore.
     *
     *
     * @param sslMetadata
     * @param caPrivateKey
     * @param caCert
@@ -544,15 +544,15 @@
            X500Name webDN = buildDistinguishedName(sslMetadata);
            X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
            X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
                    issuerDN,
                    BigInteger.valueOf(System.currentTimeMillis()),
                    BigInteger.valueOf(System.currentTimeMillis()),
                    sslMetadata.notBefore,
                    sslMetadata.notAfter,
                    webDN,
                    pair.getPublic());
            JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
            certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic()));
            certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
@@ -561,7 +561,7 @@
            // support alternateSubjectNames for SSL certificates
            List<GeneralName> altNames = new ArrayList<GeneralName>();
            if (HttpUtils.isIpAddress(sslMetadata.commonName)) {
                altNames.add(new GeneralName(GeneralName.iPAddress, sslMetadata.commonName));
                altNames.add(new GeneralName(GeneralName.iPAddress, sslMetadata.commonName));
            }
            if (altNames.size() > 0) {
                GeneralNames subjectAltName = new GeneralNames(altNames.toArray(new GeneralName [altNames.size()]));
@@ -572,7 +572,7 @@
                    .setProvider(BC).build(caPrivateKey);
            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC)
                    .getCertificate(certBuilder.build(caSigner));
            cert.checkValidity(new Date());
            cert.verify(caCert.getPublicKey());
@@ -581,9 +581,9 @@
            serverStore.setKeyEntry(sslMetadata.commonName, pair.getPrivate(), sslMetadata.password.toCharArray(),
                    new Certificate[] { cert, caCert });
            saveKeyStore(targetStoreFile, serverStore, sslMetadata.password);
            x509log.log(MessageFormat.format("New SSL certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getSubjectDN().getName()));
            // update serial number in metadata object
            sslMetadata.serialNumber = cert.getSerialNumber().toString();
@@ -596,7 +596,7 @@
    /**
     * Creates a new certificate authority PKCS#12 store.  This function will
     * destroy any existing CA store.
     *
     *
     * @param metadata
     * @param storeFile
     * @param keystorePassword
@@ -606,13 +606,13 @@
    public static X509Certificate newCertificateAuthority(X509Metadata metadata, File storeFile, X509Log x509log) {
        try {
            KeyPair caPair = newKeyPair();
            ContentSigner caSigner = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPair.getPrivate());
            // clone metadata
            X509Metadata caMetadata = metadata.clone(CA_CN, metadata.password);
            X500Name issuerDN = buildDistinguishedName(caMetadata);
            // Generate self-signed certificate
            X509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder(
                    issuerDN,
@@ -621,16 +621,16 @@
                    caMetadata.notAfter,
                    issuerDN,
                    caPair.getPublic());
            JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
            caBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(caPair.getPublic()));
            caBuilder.addExtension(X509Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(caPair.getPublic()));
            caBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(true));
            caBuilder.addExtension(X509Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
            JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC);
            X509Certificate cert = converter.getCertificate(caBuilder.build(caSigner));
            // confirm the validity of the CA certificate
            cert.checkValidity(new Date());
            cert.verify(cert.getPublicKey());
@@ -639,13 +639,13 @@
            if (storeFile.exists()) {
                storeFile.delete();
            }
            // Save private key and certificate to new keystore
            KeyStore store = openKeyStore(storeFile, caMetadata.password);
            store.setKeyEntry(CA_ALIAS, caPair.getPrivate(), caMetadata.password.toCharArray(),
                    new Certificate[] { cert });
            saveKeyStore(storeFile, store, caMetadata.password);
            x509log.log(MessageFormat.format("New CA certificate {0,number,0} [{1}]", cert.getSerialNumber(), cert.getIssuerDN().getName()));
            // update serial number in metadata object
@@ -656,11 +656,11 @@
            throw new RuntimeException("Failed to generate Gitblit CA certificate!", t);
        }
    }
    /**
     * Creates a new certificate revocation list (CRL).  This function will
     * destroy any existing CRL file.
     *
     *
     * @param caRevocationList
     * @param storeFile
     * @param keystorePassword
@@ -675,7 +675,7 @@
            X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
            X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
            // build and sign CRL with CA private key
            ContentSigner signer = new JcaContentSignerBuilder(SIGNING_ALGORITHM).setProvider(BC).build(caPrivateKey);
            X509CRLHolder crl = crlBuilder.build(signer);
@@ -703,10 +703,10 @@
            throw new RuntimeException("Failed to create new certificate revocation list " + caRevocationList, e);
        }
    }
    /**
     * Imports a certificate into the trust store.
     *
     *
     * @param alias
     * @param cert
     * @param storeFile
@@ -716,33 +716,33 @@
        try {
            KeyStore store = openKeyStore(storeFile, storePassword);
            store.setCertificateEntry(alias, cert);
            saveKeyStore(storeFile, store, storePassword);
            saveKeyStore(storeFile, store, storePassword);
        } catch (Exception e) {
            throw new RuntimeException("Failed to import certificate into trust store " + storeFile, e);
        }
    }
    /**
     * Creates a new client certificate PKCS#12 and PEM store.  Any existing
     * stores are destroyed.  After generation, the certificates are bundled
     * into a zip file with a personalized README file.
     *
     * The zip file reference is returned.
     *
     *
     * The zip file reference is returned.
     *
     * @param clientMetadata a container for dynamic parameters needed for generation
     * @param caKeystoreFile
     * @param caKeystorePassword
     * @param x509log
     * @return a zip file containing the P12, PEM, and personalized README
     */
    public static File newClientBundle(X509Metadata clientMetadata, File caKeystoreFile,
    public static File newClientBundle(X509Metadata clientMetadata, File caKeystoreFile,
            String caKeystorePassword, X509Log x509log) {
        try {
            // read the Gitblit CA key and certificate
            KeyStore store = openKeyStore(caKeystoreFile, caKeystorePassword);
            PrivateKey caPrivateKey = (PrivateKey) store.getKey(CA_ALIAS, caKeystorePassword.toCharArray());
            X509Certificate caCert = (X509Certificate) store.getCertificate(CA_ALIAS);
            // generate the P12 and PEM files
            File targetFolder = new File(caKeystoreFile.getParentFile(), clientMetadata.commonName);
            X509Certificate cert = newClientCertificate(clientMetadata, caPrivateKey, caCert, targetFolder);
@@ -750,7 +750,7 @@
            // process template message
            String readme = processTemplate(new File(caKeystoreFile.getParentFile(), "instructions.tmpl"), clientMetadata);
            // Create a zip bundle with the p12, pem, and a personalized readme
            File zipFile = new File(targetFolder, clientMetadata.commonName + ".zip");
            if (zipFile.exists()) {
@@ -764,24 +764,24 @@
                    zos.putNextEntry(new ZipEntry(p12File.getName()));
                    zos.write(FileUtils.readContent(p12File));
                    zos.closeEntry();
                }
                }
                File pemFile = new File(targetFolder, clientMetadata.commonName + ".pem");
                if (pemFile.exists()) {
                    zos.putNextEntry(new ZipEntry(pemFile.getName()));
                    zos.write(FileUtils.readContent(pemFile));
                    zos.closeEntry();
                }
                // include user's public certificate
                zos.putNextEntry(new ZipEntry(clientMetadata.commonName + ".cer"));
                zos.write(cert.getEncoded());
                zos.closeEntry();
                // include CA public certificate
                zos.putNextEntry(new ZipEntry("ca.cer"));
                zos.write(caCert.getEncoded());
                zos.closeEntry();
                if (readme != null) {
                    zos.putNextEntry(new ZipEntry("README.TXT"));
                    zos.write(readme.getBytes("UTF-8"));
@@ -793,17 +793,17 @@
                    zos.close();
                }
            }
            return zipFile;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to generate client bundle!", t);
        }
    }
    /**
     * Creates a new client certificate PKCS#12 and PEM store.  Any existing
     * stores are destroyed.
     *
     *
     * @param clientMetadata a container for dynamic parameters needed for generation
     * @param caKeystoreFile
     * @param caKeystorePassword
@@ -814,10 +814,10 @@
            PrivateKey caPrivateKey, X509Certificate caCert, File targetFolder) {
        try {
            KeyPair pair = newKeyPair();
            X500Name userDN = buildDistinguishedName(clientMetadata);
            X500Name userDN = buildDistinguishedName(clientMetadata);
            X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(caCert).getName());
            // create a new certificate signed by the Gitblit CA certificate
            X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
                    issuerDN,
@@ -826,7 +826,7 @@
                    clientMetadata.notAfter,
                    userDN,
                    pair.getPublic());
            JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
            certBuilder.addExtension(X509Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(pair.getPublic()));
            certBuilder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
@@ -844,7 +844,7 @@
            PKCS12BagAttributeCarrier bagAttr = (PKCS12BagAttributeCarrier)pair.getPrivate();
            bagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
                    extUtils.createSubjectKeyIdentifier(pair.getPublic()));
            // confirm the validity of the user certificate
            userCert.checkValidity();
            userCert.verify(caCert.getPublicKey());
@@ -854,7 +854,7 @@
            verifyChain(userCert, caCert);
            targetFolder.mkdirs();
            // save certificate, stamped with unique name
            String date = new SimpleDateFormat("yyyyMMdd").format(new Date());
            String id = date;
@@ -865,7 +865,7 @@
                certFile = new File(targetFolder, id + ".cer");
                count++;
            }
            // save user private key, user certificate and CA certificate to a PKCS#12 store
            File p12File = new File(targetFolder, clientMetadata.commonName + ".p12");
            if (p12File.exists()) {
@@ -873,9 +873,9 @@
            }
            KeyStore userStore = openKeyStore(p12File, clientMetadata.password);
            userStore.setKeyEntry(MessageFormat.format("Gitblit ({0}) {1} {2}", clientMetadata.serverHostname, clientMetadata.userDisplayname, id), pair.getPrivate(), null, new Certificate [] { userCert });
            userStore.setCertificateEntry(MessageFormat.format("Gitblit ({0}) Certificate Authority", clientMetadata.serverHostname), caCert);
            userStore.setCertificateEntry(MessageFormat.format("Gitblit ({0}) Certificate Authority", clientMetadata.serverHostname), caCert);
            saveKeyStore(p12File, userStore, clientMetadata.password);
            // save user private key, user certificate, and CA certificate to a PEM store
            File pemFile = new File(targetFolder, clientMetadata.commonName + ".pem");
            if (pemFile.exists()) {
@@ -887,22 +887,22 @@
            pemWriter.writeObject(caCert);
            pemWriter.flush();
            pemWriter.close();
            // save certificate after successfully creating the key stores
            saveCertificate(userCert, certFile);
            // update serial number in metadata object
            clientMetadata.serialNumber = userCert.getSerialNumber().toString();
            return userCert;
        } catch (Throwable t) {
            throw new RuntimeException("Failed to generate client certificate!", t);
        }
    }
    /**
     * Verifies a certificate's chain to ensure that it will function properly.
     *
     *
     * @param testCert
     * @param additionalCerts
     * @return
@@ -913,19 +913,19 @@
            if (isSelfSigned(testCert)) {
                throw new RuntimeException("The certificate is self-signed.  Nothing to verify.");
            }
            // Prepare a set of all certificates
            // chain builder must have all certs, including cert to validate
            // http://stackoverflow.com/a/10788392
            Set<X509Certificate> certs = new HashSet<X509Certificate>();
            certs.add(testCert);
            certs.addAll(Arrays.asList(additionalCerts));
            // Attempt to build the certification chain and verify it
            // Create the selector that specifies the starting certificate
            X509CertSelector selector = new X509CertSelector();
            X509CertSelector selector = new X509CertSelector();
            selector.setCertificate(testCert);
            // Create the trust anchors (set of root CA certificates)
            Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
            for (X509Certificate cert : additionalCerts) {
@@ -933,16 +933,16 @@
                    trustAnchors.add(new TrustAnchor(cert, null));
                }
            }
            // Configure the PKIX certificate builder
            PKIXBuilderParameters pkixParams = new PKIXBuilderParameters(trustAnchors, selector);
            pkixParams.setRevocationEnabled(false);
            pkixParams.addCertStore(CertStore.getInstance("Collection", new CollectionCertStoreParameters(certs), BC));
            // Build and verify the certification chain
            CertPathBuilder builder = CertPathBuilder.getInstance("PKIX", BC);
            PKIXCertPathBuilderResult verifiedCertChain = (PKIXCertPathBuilderResult) builder.build(pkixParams);
            // The chain is built and verified
            return verifiedCertChain;
        } catch (CertPathBuilderException e) {
@@ -951,10 +951,10 @@
            throw new RuntimeException("Error verifying the certificate: " + testCert.getSubjectX500Principal(), e);
        }
    }
    /**
     * Checks whether given X.509 certificate is self-signed.
     *
     *
     * @param cert
     * @return true if the certificate is self-signed
     */
@@ -970,7 +970,7 @@
            throw new RuntimeException(e);
        }
    }
    public static String processTemplate(File template, X509Metadata metadata) {
        String content = null;
        if (template.exists()) {
@@ -993,10 +993,10 @@
        }
        return content;
    }
    /**
     * Revoke a certificate.
     *
     *
     * @param cert
     * @param reason
     * @param caRevocationList
@@ -1019,10 +1019,10 @@
        }
        return false;
    }
    /**
     * Revoke a certificate.
     *
     *
     * @param cert
     * @param reason
     * @param caRevocationList
@@ -1036,16 +1036,16 @@
            X500Name issuerDN = new X500Name(PrincipalUtil.getIssuerX509Principal(cert).getName());
            X509v2CRLBuilder crlBuilder = new X509v2CRLBuilder(issuerDN, new Date());
            if (caRevocationList.exists()) {
                byte [] data = FileUtils.readContent(caRevocationList);
                byte [] data = FileUtils.readContent(caRevocationList);
                X509CRLHolder crl = new X509CRLHolder(data);
                crlBuilder.addCRL(crl);
            }
            crlBuilder.addCRLEntry(cert.getSerialNumber(), new Date(), reason.ordinal());
            // build and sign CRL with CA private key
            ContentSigner signer = new JcaContentSignerBuilder("SHA1WithRSA").setProvider(BC).build(caPrivateKey);
            X509CRLHolder crl = crlBuilder.build(signer);
            File tmpFile = new File(caRevocationList.getParentFile(), Long.toHexString(System.currentTimeMillis()) + ".tmp");
            FileOutputStream fos = null;
            try {
@@ -1057,7 +1057,7 @@
                    caRevocationList.delete();
                }
                tmpFile.renameTo(caRevocationList);
            } finally {
                if (fos != null) {
                    fos.close();
@@ -1066,7 +1066,7 @@
                    tmpFile.delete();
                }
            }
            x509log.log(MessageFormat.format("Revoked certificate {0,number,0} reason: {1} [{2}]",
                    cert.getSerialNumber(), reason.toString(), cert.getSubjectDN().getName()));
            return true;
@@ -1076,10 +1076,10 @@
        }
        return false;
    }
    /**
     * Returns true if the certificate has been revoked.
     *
     *
     * @param cert
     * @param caRevocationList
     * @return true if the certificate is revoked
@@ -1107,7 +1107,7 @@
        }
        return false;
    }
    public static X509Metadata getMetadata(X509Certificate cert) {
        // manually split DN into OID components
        // this is instead of parsing with LdapName which:
@@ -1121,7 +1121,7 @@
            String data = val[1].trim();
            oids.put(oid, data);
        }
        X509Metadata metadata = new X509Metadata(oids.get("CN"), "whocares");
        metadata.oids.putAll(oids);
        metadata.serialNumber = cert.getSerialNumber().toString();
src/main/java/com/gitblit/wicket/AuthorizationStrategy.java
@@ -77,7 +77,7 @@
    @Override
    public void onUnauthorizedInstantiation(Component component) {
        if (component instanceof BasePage) {
            throw new RestartResponseException(GitBlitWebApp.HOME_PAGE_CLASS);
        }
src/main/java/com/gitblit/wicket/CacheControl.java
@@ -21,20 +21,20 @@
/**
 * Page attribute to control what date as last-modified for the browser cache.
 *
 *
 * http://betterexplained.com/articles/how-to-optimize-your-site-with-http-caching
 * https://developers.google.com/speed/docs/best-practices/caching
 *
 *
 * @author James Moger
 *
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheControl {
    public static enum LastModified {
        BOOT, ACTIVITY, PROJECT, REPOSITORY, COMMIT, NONE
    }
    LastModified value() default LastModified.NONE;
}
src/main/java/com/gitblit/wicket/ExternalImage.java
@@ -27,6 +27,7 @@
        super(id, new Model<String>(url));
    }
    @Override
    protected void onComponentTag(ComponentTag tag) {
        super.onComponentTag(tag);
        checkComponentTag(tag, "img");
src/main/java/com/gitblit/wicket/GitBlitWebSession.java
@@ -42,27 +42,28 @@
    private UserModel user;
    private String errorMessage;
    private String requestUrl;
    private AtomicBoolean isForking;
    public AuthenticationType authenticationType;
    public GitBlitWebSession(Request request) {
        super(request);
        isForking = new AtomicBoolean();
        authenticationType = AuthenticationType.CREDENTIALS;
    }
    @Override
    public void invalidate() {
        super.invalidate();
        user = null;
    }
    /**
     * Cache the requested protected resource pending successful authentication.
     *
     *
     * @param pageClass
     */
    public void cacheRequest(Class<? extends Page> pageClass) {
@@ -81,14 +82,14 @@
            bind();
        }
    }
    /**
     * Continue any cached request.  This is used when a request for a protected
     * resource is aborted/redirected pending proper authentication.  Gitblit
     * no longer uses Wicket's built-in mechanism for this because of Wicket's
     * failure to properly handle parameters with forward-slashes.  This is a
     * constant source of headaches with Wicket.
     *
     *
     * @return false if there is no cached request to process
     */
    public boolean continueRequest() {
@@ -110,7 +111,7 @@
        }
        return user.canAdmin();
    }
    public String getUsername() {
        return user == null ? "anonymous" : user.username;
    }
@@ -150,11 +151,11 @@
        errorMessage = null;
        return msg;
    }
    public boolean isForking() {
        return isForking.get();
    }
    public void isForking(boolean val) {
        isForking.set(val);
    }
src/main/java/com/gitblit/wicket/GitblitParamUrlCodingStrategy.java
@@ -31,12 +31,12 @@
/**
 * Simple subclass of mixed parameter url coding strategy that works around the
 * encoded forward-slash issue that is present in some servlet containers.
 *
 *
 * https://issues.apache.org/jira/browse/WICKET-1303
 * http://tomcat.apache.org/security-6.html
 *
 *
 * @author James Moger
 *
 *
 */
public class GitblitParamUrlCodingStrategy extends MixedParamUrlCodingStrategy {
@@ -44,7 +44,7 @@
    /**
     * Construct.
     *
     *
     * @param <C>
     * @param mountPath
     *            mount path (not empty)
@@ -60,11 +60,12 @@
    /**
     * Url encodes a string that is mean for a URL path (e.g., between slashes)
     *
     *
     * @param string
     *            string to be encoded
     * @return encoded string
     */
    @Override
    protected String urlEncodePathComponent(String string) {
        char altChar = GitBlit.getChar(Keys.web.forwardSlashCharacter, '/');
        if (altChar != '/') {
@@ -76,10 +77,11 @@
    /**
     * Returns a decoded value of the given value (taken from a URL path
     * section)
     *
     *
     * @param value
     * @return Decodes the value
     */
    @Override
    protected String urlDecodePathComponent(String value) {
        char altChar = GitBlit.getChar(Keys.web.forwardSlashCharacter, '/');
        if (altChar != '/') {
@@ -90,7 +92,7 @@
    /**
     * Gets the decoded request target.
     *
     *
     * @param requestParameters
     *            the request parameters
     * @return the decoded request target
src/main/java/com/gitblit/wicket/GitblitRedirectException.java
@@ -26,9 +26,9 @@
 * This exception bypasses the servlet container rewriting relative redirect
 * urls.  The container can and does decode the carefully crafted %2F path
 * separators on a redirect.  :(  Bad, bad servlet container.
 *
 *
 * org.eclipse.jetty.server.Response#L447: String path=uri.getDecodedPath();
 *
 *
 * @author James Moger
 */
public class GitblitRedirectException extends AbstractRestartResponseException {
src/main/java/com/gitblit/wicket/GitblitWicketFilter.java
@@ -32,21 +32,22 @@
import com.gitblit.utils.StringUtils;
/**
 *
 *
 * Customization of the WicketFilter to allow smart browser-side caching of
 * some pages.
 *
 *
 * @author James Moger
 *
 */
public class GitblitWicketFilter extends WicketFilter {
    /**
     * Determines the last-modified date of the requested resource.
     *
     *
     * @param servletRequest
     * @return The last modified time stamp
     */
    @Override
    protected long getLastModified(final HttpServletRequest servletRequest)    {
        final String pathInfo = getRelativePath(servletRequest);
        if (Strings.isEmpty(pathInfo))
@@ -55,10 +56,10 @@
        if (lastModified > -1) {
            return lastModified;
        }
        // try to match request against registered CacheControl pages
        String [] paths = pathInfo.split("/");
        String page = paths[0];
        String repo = "";
        String commitId = "";
@@ -68,14 +69,14 @@
        if (paths.length >= 3) {
            commitId = paths[2];
        }
        if (!StringUtils.isEmpty(servletRequest.getParameter("r"))) {
            repo = servletRequest.getParameter("r");
        }
        if (!StringUtils.isEmpty(servletRequest.getParameter("h"))) {
            commitId = servletRequest.getParameter("h");
        }
        repo = repo.replace("%2f", "/").replace("%2F", "/").replace(GitBlit.getChar(Keys.web.forwardSlashCharacter, '/'), '/');
        GitBlitWebApp app = (GitBlitWebApp) getWebApplication();
@@ -116,7 +117,7 @@
                    // no commit id, use boot date
                    return bootDate.getTime();
                } else {
                    // last modified date is the commit date
                    // last modified date is the commit date
                    Repository r = null;
                    try {
                        // return the timestamp of the associated commit
@@ -138,7 +139,7 @@
            default:
                break;
            }
        }
        }
        return -1;
    }
src/main/java/com/gitblit/wicket/PageRegistration.java
@@ -26,9 +26,9 @@
/**
 * Represents a page link registration for the topbar.
 *
 *
 * @author James Moger
 *
 *
 */
public class PageRegistration implements Serializable {
    private static final long serialVersionUID = 1L;
@@ -46,7 +46,7 @@
            PageParameters params) {
        this(translationKey, pageClass, params, false);
    }
    public PageRegistration(String translationKey, Class<? extends WebPage> pageClass,
            PageParameters params, boolean hiddenPhone) {
        this.translationKey = translationKey;
@@ -57,9 +57,9 @@
    /**
     * Represents a page link to a non-Wicket page. Might be external.
     *
     *
     * @author James Moger
     *
     *
     */
    public static class OtherPageLink extends PageRegistration {
@@ -71,7 +71,7 @@
            super(translationKey, null);
            this.url = url;
        }
        public OtherPageLink(String translationKey, String url, boolean hiddenPhone) {
            super(translationKey, null, null, hiddenPhone);
            this.url = url;
@@ -80,9 +80,9 @@
    /**
     * Represents a DropDownMenu for the topbar
     *
     *
     * @author James Moger
     *
     *
     */
    public static class DropDownMenuRegistration extends PageRegistration {
@@ -98,9 +98,9 @@
    /**
     * A MenuItem for the DropDownMenu.
     *
     *
     * @author James Moger
     *
     *
     */
    public static class DropDownMenuItem implements Serializable {
@@ -121,7 +121,7 @@
        /**
         * Standard Menu Item constructor.
         *
         *
         * @param displayText
         * @param parameter
         * @param value
@@ -132,7 +132,7 @@
        /**
         * Standard Menu Item constructor that preserves aggregate parameters.
         *
         *
         * @param displayText
         * @param parameter
         * @param value
@@ -219,14 +219,14 @@
            return displayText;
        }
    }
    public static class DropDownToggleItem extends DropDownMenuItem {
        private static final long serialVersionUID = 1L;
        /**
         * Toggle Menu Item constructor that preserves aggregate parameters.
         *
         *
         * @param displayText
         * @param parameter
         * @param value
src/main/java/com/gitblit/wicket/SessionlessForm.java
@@ -35,35 +35,35 @@
 * This class is used to create a stateless form that can POST or GET to a
 * bookmarkable page regardless of the pagemap and even after session expiration
 * or a server restart.
 *
 *
 * The trick is to embed "wicket:bookmarkablePage" as a hidden field of the form.
 * Wicket already has logic to extract this parameter when it is trying
 * to determine which page should receive the request.
 *
 *
 * The parameters of the containing page can optionally be included as hidden
 * fields in this form.  Note that if a page parameter's name collides with any
 * fields in this form.  Note that if a page parameter's name collides with any
 * child's wicket:id in this form then the page parameter is excluded.
 *
 *
 * @author James Moger
 *
 */
public class SessionlessForm<T> extends StatelessForm<T> {
    private static final long serialVersionUID = 1L;
    private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
    private final Class<? extends BasePage> pageClass;
    private final PageParameters pageParameters;
    private final Logger log = LoggerFactory.getLogger(SessionlessForm.class);
    /**
     * Sessionless forms must have a bookmarkable page class.  A bookmarkable
     * page is defined as a page that has only a default and/or a PageParameter
     * constructor.
     *
     *
     * @param id
     * @param bookmarkablePageClass
     */
@@ -75,7 +75,7 @@
     * Sessionless forms must have a bookmarkable page class.  A bookmarkable
     * page is defined as a page that has only a default and/or a PageParameter
     * constructor.
     *
     *
     * @param id
     * @param bookmarkablePageClass
     * @param pageParameters
@@ -87,12 +87,12 @@
        this.pageParameters = pageParameters;
    }
    /**
     * Append an additional hidden input tag that forces Wicket to correctly
     * determine the destination page class even after a session expiration or
     * a server restart.
     *
     *
     * @param markupStream
     *            The markup stream
     * @param openTag
@@ -133,10 +133,10 @@
        getResponse().write(buffer);
        super.onComponentTagBody(markupStream, openTag);
    }
    /**
     * Take URL-encoded query string value, unencode it and return HTML-escaped version
     *
     *
     * @param s
     *            value to reencode
     * @return reencoded value
src/main/java/com/gitblit/wicket/StringChoiceRenderer.java
@@ -20,14 +20,14 @@
/**
 * Choice renderer for a palette or list of string values.  This renderer
 * controls the id value of each option such that palettes are case insensitive.
 *
 *
 * @author James Moger
 *
 */
public class StringChoiceRenderer extends ChoiceRenderer<String> {
    private static final long serialVersionUID = 1L;
    public StringChoiceRenderer() {
        super("", "");
    }
src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -115,7 +115,7 @@
        default:
            setCssClass(container, "badge");
            break;
        }
        }
    }
    public static void setAlternatingBackground(Component c, int i) {
@@ -232,17 +232,17 @@
    public static Label newIcon(String wicketId, String css) {
        Label lbl = new Label(wicketId);
        setCssClass(lbl, css);
        setCssClass(lbl, css);
        return lbl;
    }
    public static Label newBlankIcon(String wicketId) {
        Label lbl = new Label(wicketId);
        setCssClass(lbl, "");
        lbl.setRenderBodyOnly(true);
        return lbl;
    }
    public static ContextRelativeResource getResource(String file) {
        return new ContextRelativeResource(file);
    }
@@ -257,6 +257,7 @@
        return new HeaderContributor(new IHeaderContributor() {
            private static final long serialVersionUID = 1L;
            @Override
            public void renderHead(IHeaderResponse response) {
                String contentType = "application/rss+xml";
@@ -509,7 +510,7 @@
    public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils) {
        return createDateLabel(wicketId, date, timeZone, timeUtils, true);
    }
    public static Label createDateLabel(String wicketId, Date date, TimeZone timeZone, TimeUtils timeUtils, boolean setCss) {
        String format = GitBlit.getString(Keys.web.datestampShortFormat, "MM/dd/yy");
        DateFormat df = new SimpleDateFormat(format);
@@ -642,6 +643,7 @@
        IChartData data = new AbstractChartData(max) {
            private static final long serialVersionUID = 1L;
            @Override
            public double[][] getData() {
                return new double[][] { commits, tags };
            }
@@ -677,6 +679,7 @@
        IChartData data = new AbstractChartData(max) {
            private static final long serialVersionUID = 1L;
            @Override
            public double[][] getData() {
                return new double[][] { x, y };
            }
src/main/java/com/gitblit/wicket/charting/GoogleChart.java
@@ -23,9 +23,9 @@
/**
 * Abstract parent class for Google Charts built with the Visualization API.
 *
 *
 * @author James Moger
 *
 *
 */
public abstract class GoogleChart implements Serializable {
@@ -57,7 +57,7 @@
    public void setHeight(int height) {
        this.height = height;
    }
    public void setShowLegend(boolean val) {
        this.showLegend = val;
    }
src/main/java/com/gitblit/wicket/charting/GoogleCharts.java
@@ -13,7 +13,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 */
package com.gitblit.wicket.charting;
import java.util.ArrayList;
@@ -26,9 +25,9 @@
 * The Google Visualization API provides interactive JavaScript based charts and
 * graphs. This class implements the JavaScript header necessary to display
 * complete graphs and charts.
 *
 *
 * @author James Moger
 *
 *
 */
public class GoogleCharts implements IHeaderContributor {
src/main/java/com/gitblit/wicket/charting/GoogleLineChart.java
@@ -19,9 +19,9 @@
/**
 * Builds an interactive line chart using the Visualization API.
 *
 *
 * @author James Moger
 *
 *
 */
public class GoogleLineChart extends GoogleChart {
src/main/java/com/gitblit/wicket/charting/GooglePieChart.java
@@ -24,9 +24,9 @@
/**
 * Builds an interactive pie chart using the Visualization API.
 *
 *
 * @author James Moger
 *
 *
 */
public class GooglePieChart extends GoogleChart {
@@ -47,15 +47,15 @@
        Collections.sort(values);
        List<ChartValue> list = new ArrayList<ChartValue>();
        int maxSlices = 10;
        if (values.size() > maxSlices) {
            list.addAll(values.subList(0,  maxSlices));
        } else {
            list.addAll(values);
        }
        StringBuilder colors = new StringBuilder("colors:[");
        for (int i = 0; i < list.size(); i++) {
            ChartValue value = list.get(i);
src/main/java/com/gitblit/wicket/charting/SecureChart.java
@@ -43,7 +43,7 @@
/**
 * This is a fork of org.wicketstuff.googlecharts.Chart whose only purpose
 * is to build https urls instead of http urls.
 *
 *
 * @author Daniel Spiewak
 * @author James Moger
 */
@@ -140,7 +140,7 @@
        url.append(param).append('=').append(value);
    }
    private CharSequence convert(ChartDataEncoding encoding, double value, double max) {
        switch (encoding) {
        case TEXT:
src/main/java/com/gitblit/wicket/charting/SecureChartDataEncoding.java
@@ -19,14 +19,15 @@
/**
 * This class is a pristine fork of org.wicketstuff.googlecharts.ChartDataEncoding
 * to bring the package-protected convert methods to SecureChart.
 *
 *
 * @author Daniel Spiewak
 */
public enum SecureChartDataEncoding {
    SIMPLE("s", "", ",") {
        CharSequence convert(double value, double max) {
        @Override
        CharSequence convert(double value, double max) {
            if (value < 0) {
                return "_";
            }
@@ -42,7 +43,8 @@
    },
    TEXT("t", ",", "|") {
        CharSequence convert(double value, double max) {
        @Override
        CharSequence convert(double value, double max) {
            if (value < 0) {
                value = -1;
            }
@@ -56,7 +58,8 @@
    },
    EXTENDED("e", "", ",") {
        CharSequence convert(double value, double max) {
        @Override
        CharSequence convert(double value, double max) {
            if (value < 0) {
                return "__";
            }
src/main/java/com/gitblit/wicket/freemarker/Freemarker.java
@@ -27,18 +27,18 @@
public class Freemarker {
    private static final Configuration fm;
    static {
        fm = new Configuration();
        fm.setObjectWrapper(new DefaultObjectWrapper());
        fm.setOutputEncoding("UTF-8");
        fm.setClassForTemplateLoading(Freemarker.class, "templates");
    }
    public static Template getTemplate(String name) throws IOException {
        return fm.getTemplate(name);
    }
    public static void evaluate(Template template, Map<String, Object> values, Writer out) throws TemplateException, IOException {
        template.process(values, out);
    }
src/main/java/com/gitblit/wicket/freemarker/FreemarkerPanel.java
@@ -43,9 +43,9 @@
 * snippet injector for something like a CMS.  There are some cases where Wicket
 * is not flexible enough to generate content, especially when you need to generate
 * hybrid HTML/JS content outside the scope of Wicket.
 *
 *
 * @author James Moger
 *
 *
 */
@SuppressWarnings("unchecked")
public class FreemarkerPanel extends Panel
@@ -62,10 +62,10 @@
    private transient String stackTraceAsString;
    private transient String evaluatedTemplate;
    /**
     * Construct.
     *
     *
     * @param id
     *            Component id
     * @param template
@@ -77,10 +77,10 @@
    {
        this(id, template, Model.ofMap(values));
    }
    /**
     * Construct.
     *
     *
     * @param id
     *            Component id
     * @param templateResource
@@ -96,7 +96,7 @@
    /**
     * Gets the Freemarker template.
     *
     *
     * @return the Freemarker template
     */
    private Template getTemplate()
@@ -155,7 +155,7 @@
    /**
     * Either print or rethrow the throwable.
     *
     *
     * @param exception
     *            the cause
     * @param markupStream
@@ -179,7 +179,7 @@
    /**
     * Gets whether to escape HTML characters.
     *
     *
     * @return whether to escape HTML characters. The default value is false.
     */
    public void setEscapeHtml(boolean value)
@@ -189,7 +189,7 @@
    /**
     * Evaluates the template and returns the result.
     *
     *
     * @param templateReader
     *            used to read the template
     * @return the result of evaluating the velocity template
@@ -237,7 +237,7 @@
    /**
     * Gets whether to parse the resulting Wicket markup.
     *
     *
     * @return whether to parse the resulting Wicket markup. The default is false.
     */
    public void setParseGeneratedMarkup(boolean value)
@@ -256,7 +256,7 @@
     * want them to be able to have them correct them while the rest of the application keeps on
     * working.
     * </p>
     *
     *
     * @return Whether any Freemarker exceptions should be thrown or trapped. The default is false.
     */
    public void setThrowFreemarkerExceptions(boolean value)
@@ -268,6 +268,7 @@
     * @see org.apache.wicket.markup.IMarkupResourceStreamProvider#getMarkupResourceStream(org.apache
     *      .wicket.MarkupContainer, java.lang.Class)
     */
    @Override
    public final IResourceStream getMarkupResourceStream(MarkupContainer container,
            Class< ? > containerClass)
    {
@@ -289,6 +290,7 @@
     * @see org.apache.wicket.markup.IMarkupCacheKeyProvider#getCacheKey(org.apache.wicket.
     *      MarkupContainer, java.lang.Class)
     */
    @Override
    public final String getCacheKey(MarkupContainer container, Class< ? > containerClass)
    {
        // don't cache the evaluated template
src/main/java/com/gitblit/wicket/ng/NgController.java
@@ -13,7 +13,6 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 */
package com.gitblit.wicket.ng;
import java.text.MessageFormat;
@@ -31,23 +30,23 @@
 * Simple AngularJS data controller which injects scoped objects as static,
 * embedded JSON within the generated page.  This allows use of AngularJS
 * client-side databinding (magic) with server-generated pages.
 *
 *
 * @author James Moger
 *
 *
 */
public class NgController implements IHeaderContributor {
    private static final long serialVersionUID = 1L;
    final String name;
    final Map<String, Object> variables;
    public NgController(String name) {
        this.name = name;
        this.variables = new HashMap<String, Object>();
    }
    public void addVariable(String name, Object o) {
        variables.put(name,  o);
    }
@@ -69,7 +68,7 @@
            line(sb, MessageFormat.format("\t$scope.{0} = {1};", var, json));
        }
        line(sb, "}");
        response.renderJavascript(sb.toString(), null);
    }
src/main/java/com/gitblit/wicket/pages/ActivityPage.java
@@ -51,9 +51,9 @@
/**
 * Activity Page shows a list of recent commits across all visible Gitblit
 * repositories.
 *
 *
 * @author James Moger
 *
 *
 */
@CacheControl(LastModified.ACTIVITY)
@@ -72,7 +72,7 @@
        // determine repositories to view and retrieve the activity
        List<RepositoryModel> models = getRepositories(params);
        List<Activity> recentActivity = ActivityUtils.getRecentActivity(models,
        List<Activity> recentActivity = ActivityUtils.getRecentActivity(models,
                daysBack, objectId, getTimeZone());
        String headerPattern;
@@ -91,7 +91,7 @@
                headerPattern = getString("gb.recentActivityStats");
            }
        }
        if (recentActivity.size() == 0) {
            // no activity, skip graphs and activity panel
            add(new Label("subheader", MessageFormat.format(headerPattern,
@@ -157,7 +157,7 @@
    /**
     * Creates the daily activity line chart, the active repositories pie chart,
     * and the active authors pie chart
     *
     *
     * @param recentActivity
     * @return
     */
src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -67,7 +67,7 @@
public abstract class BasePage extends SessionPage {
    private final Logger logger;
    private transient TimeUtils timeUtils;
    public BasePage() {
@@ -81,24 +81,24 @@
        logger = LoggerFactory.getLogger(getClass());
        customizeHeader();
    }
    private void customizeHeader() {
        if (GitBlit.getBoolean(Keys.web.useResponsiveLayout, true)) {
            add(CSSPackageResource.getHeaderContribution("bootstrap/css/bootstrap-responsive.css"));
        }
    }
    protected String getLanguageCode() {
        return GitBlitWebSession.get().getLocale().getLanguage();
    }
    protected String getCountryCode() {
        return GitBlitWebSession.get().getLocale().getCountry().toLowerCase();
    }
    protected TimeUtils getTimeUtils() {
        if (timeUtils == null) {
            ResourceBundle bundle;
            ResourceBundle bundle;
            try {
                bundle = ResourceBundle.getBundle("com.gitblit.wicket.GitBlitWebApp", GitBlitWebSession.get().getLocale());
            } catch (Throwable t) {
@@ -108,7 +108,7 @@
        }
        return timeUtils;
    }
    @Override
    protected void onBeforeRender() {
        if (GitBlit.isDebugMode()) {
@@ -126,7 +126,7 @@
        }
        super.onAfterRender();
    }
    @Override
    protected void setHeaders(WebResponse response)    {
        int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
@@ -140,11 +140,11 @@
            super.setHeaders(response);
        }
    }
    /**
     * Sets the last-modified header date, if appropriate, for this page.  The
     * date used is determined by the CacheControl annotation.
     *
     *
     */
    protected void setLastModified() {
        if (getClass().isAnnotationPresent(CacheControl.class)) {
@@ -164,24 +164,24 @@
            }
        }
    }
    /**
     * Sets the last-modified header field and the expires field.
     *
     *
     * @param when
     */
    protected final void setLastModified(Date when) {
        if (when == null) {
            return;
        }
        if (when.before(GitBlit.getBootDate())) {
            // last-modified can not be before the Gitblit boot date
            // this helps ensure that pages are properly refreshed after a
            // server config change
            when = GitBlit.getBootDate();
        }
        int expires = GitBlit.getInteger(Keys.web.pageCacheExpires, 0);
        WebResponse response = (WebResponse) getResponse();
        response.setLastModifiedTime(Time.valueOf(when));
@@ -232,7 +232,7 @@
        }
        return map;
    }
    protected Map<AccessPermission, String> getAccessPermissions() {
        Map<AccessPermission, String> map = new LinkedHashMap<AccessPermission, String>();
        for (AccessPermission type : AccessPermission.values()) {
@@ -265,7 +265,7 @@
        }
        return map;
    }
    protected Map<FederationStrategy, String> getFederationTypes() {
        Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>();
        for (FederationStrategy type : FederationStrategy.values()) {
@@ -283,7 +283,7 @@
        }
        return map;
    }
    protected Map<AuthorizationControl, String> getAuthorizationControls() {
        Map<AuthorizationControl, String> map = new LinkedHashMap<AuthorizationControl, String>();
        for (AuthorizationControl type : AuthorizationControl.values()) {
@@ -309,13 +309,13 @@
        HttpServletRequest req = servletWebRequest.getHttpServletRequest();
        return req.getServerName();
    }
    protected List<ProjectModel> getProjectModels() {
        final UserModel user = GitBlitWebSession.get().getUser();
        List<ProjectModel> projects = GitBlit.self().getProjectModels(user, true);
        return projects;
    }
    protected List<ProjectModel> getProjects(PageParameters params) {
        if (params == null) {
            return getProjectModels();
@@ -400,7 +400,7 @@
    public void warn(String message, Throwable t) {
        logger.warn(message, t);
    }
    public void error(String message, boolean redirect) {
        error(message, null, redirect ? getApplication().getHomePage() : null);
    }
@@ -408,11 +408,11 @@
    public void error(String message, Throwable t, boolean redirect) {
        error(message, t, getApplication().getHomePage());
    }
    public void error(String message, Throwable t, Class<? extends Page> toPage) {
        error(message, t, toPage, null);
    }
    public void error(String message, Throwable t, Class<? extends Page> toPage, PageParameters params) {
        if (t == null) {
            logger.error(message  + " for " + GitBlitWebSession.get().getUsername());
src/main/java/com/gitblit/wicket/pages/BlamePage.java
@@ -75,7 +75,7 @@
                "EEEE, MMMM d, yyyy HH:mm Z");
        final DateFormat df = new SimpleDateFormat(format);
        df.setTimeZone(getTimeZone());
        PathModel pathModel = null;
        List<PathModel> paths = JGitUtils.getFilesInPath(getRepository(), StringUtils.getRootPath(blobPath), commit);
        for (PathModel path : paths) {
@@ -84,15 +84,15 @@
                break;
            }
        }
        if (pathModel == null) {
            add(new Label("annotation").setVisible(false));
            add(new Label("missingBlob", missingBlob(blobPath, commit)).setEscapeModelStrings(false));
            return;
        }
        add(new Label("missingBlob").setVisible(false));
        List<AnnotatedLine> lines = DiffUtils.blame(getRepository(), blobPath, objectId);
        ListDataProvider<AnnotatedLine> blameDp = new ListDataProvider<AnnotatedLine>(lines);
        DataView<AnnotatedLine> blameView = new DataView<AnnotatedLine>("annotation", blameDp) {
@@ -102,6 +102,7 @@
            private boolean showInitials = true;
            private String zeroId = ObjectId.zeroId().getName();
            @Override
            public void populateItem(final Item<AnnotatedLine> item) {
                AnnotatedLine entry = item.getModelObject();
                item.add(new Label("line", "" + entry.lineNumber));
@@ -157,12 +158,12 @@
    protected String getPageName() {
        return getString("gb.blame");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return TreePage.class;
    }
    protected String missingBlob(String blobPath, RevCommit commit) {
        StringBuilder sb = new StringBuilder();
        sb.append("<div class=\"alert alert-error\">");
src/main/java/com/gitblit/wicket/pages/BlobDiffPage.java
@@ -80,7 +80,7 @@
    protected String getPageName() {
        return getString("gb.diff");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return TreePage.class;
src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -33,9 +33,9 @@
import com.gitblit.utils.JGitUtils;
import com.gitblit.utils.StringUtils;
import com.gitblit.wicket.CacheControl;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.ExternalImage;
import com.gitblit.wicket.WicketUtils;
import com.gitblit.wicket.CacheControl.LastModified;
import com.gitblit.wicket.panels.CommitHeaderPanel;
import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@@ -50,7 +50,7 @@
        Repository r = getRepository();
        final String blobPath = WicketUtils.getPath(params);
        String [] encodings = GitBlit.getEncodings();
        if (StringUtils.isEmpty(blobPath)) {
            // blob by objectid
@@ -153,7 +153,7 @@
            }
        }
    }
    protected String missingBlob(String blobPath, RevCommit commit) {
        StringBuilder sb = new StringBuilder();
        sb.append("<div class=\"alert alert-error\">");
@@ -165,11 +165,11 @@
    protected String generateSourceView(String source, String extension, boolean prettyPrint) {
        String [] lines = source.split("\n");
        StringBuilder sb = new StringBuilder();
        sb.append("<!-- start blob table -->");
        sb.append("<table width=\"100%\"><tbody><tr>");
        // nums column
        sb.append("<!-- start nums column -->");
        sb.append("<td id=\"nums\">");
@@ -181,7 +181,7 @@
        sb.append("</pre>");
        sb.append("<!-- end nums column -->");
        sb.append("</td>");
        sb.append("<!-- start lines column -->");
        sb.append("<td id=\"lines\">");
        sb.append("<div class=\"sourceview\">");
@@ -191,9 +191,9 @@
            sb.append("<pre class=\"plainprint\">");
        }
        lines = StringUtils.escapeForHtml(source, true).split("\n");
        sb.append("<table width=\"100%\"><tbody>");
        String linePattern = "<tr class=\"{0}\"><td><div><span class=\"line\">{1}</span></div>\r</tr>";
        for (int i = 0; i < lines.length; i++) {
            String line = lines[i].replace('\r', ' ');
@@ -208,10 +208,10 @@
        sb.append("</div>");
        sb.append("</td>");
        sb.append("<!-- end lines column -->");
        sb.append("</tr></tbody></table>");
        sb.append("<!-- end blob table -->");
        return sb.toString();
    }
@@ -219,7 +219,7 @@
    protected String getPageName() {
        return getString("gb.view");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return TreePage.class;
src/main/java/com/gitblit/wicket/pages/ChangePasswordPage.java
@@ -50,13 +50,13 @@
            // no authentication enabled
            throw new RestartResponseException(getApplication().getHomePage());
        }
        UserModel user = GitBlitWebSession.get().getUser();
        UserModel user = GitBlitWebSession.get().getUser();
        if (!GitBlit.self().supportsCredentialChanges(user)) {
            error(MessageFormat.format(getString("gb.userServiceDoesNotPermitPasswordChanges"),
                    GitBlit.getString(Keys.realm.userService, "${baseFolder}/users.conf")), true);
        }
        setupPage(getString("gb.changePassword"), user.username);
        StatelessForm<Void> form = new StatelessForm<Void>("passwordForm") {
src/main/java/com/gitblit/wicket/pages/CommitDiffPage.java
@@ -99,6 +99,7 @@
        DataView<GitNote> notesView = new DataView<GitNote>("notes", notesDp) {
            private static final long serialVersionUID = 1L;
            @Override
            public void populateItem(final Item<GitNote> item) {
                GitNote entry = item.getModelObject();
                item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
@@ -112,7 +113,7 @@
            }
        };
        add(notesView.setVisible(notes.size() > 0));
        // changed paths list
        add(new CommitLegendPanel("commitLegend", diff.stat.paths));
        ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(diff.stat.paths);
@@ -120,6 +121,7 @@
            private static final long serialVersionUID = 1L;
            int counter;
            @Override
            public void populateItem(final Item<PathChangeModel> item) {
                final PathChangeModel entry = item.getModelObject();
                Label changeType = new Label("changeType", "");
@@ -176,7 +178,7 @@
                            .newPathParameter(repositoryName, entry.commitId, entry.path))
                            .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
                }
                WicketUtils.setAlternatingBackground(item, counter);
                counter++;
            }
@@ -189,7 +191,7 @@
    protected String getPageName() {
        return getString("gb.commitdiff");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return LogPage.class;
src/main/java/com/gitblit/wicket/pages/CommitPage.java
@@ -56,7 +56,7 @@
        Repository r = getRepository();
        RevCommit c = getCommit();
        List<String> parents = new ArrayList<String>();
        if (c.getParentCount() > 0) {
            for (RevCommit parent : c.getParents()) {
@@ -86,7 +86,7 @@
        add(createPersonPanel("commitAuthor", c.getAuthorIdent(), Constants.SearchType.AUTHOR));
        add(WicketUtils.createTimestampLabel("commitAuthorDate", c.getAuthorIdent().getWhen(),
                getTimeZone(), getTimeUtils()));
        // committer
        add(createPersonPanel("commitCommitter", c.getCommitterIdent(), Constants.SearchType.COMMITTER));
        add(WicketUtils.createTimestampLabel("commitCommitterDate",
@@ -98,7 +98,7 @@
                newCommitParameter()));
        add(new BookmarkablePageLink<Void>("treeLink", TreePage.class, newCommitParameter()));
        final String baseUrl = WicketUtils.getGitblitURL(getRequest());
        add(new CompressedDownloadsPanel("compressedLinks", baseUrl, repositoryName, objectId, null));
        // Parent Commits
@@ -106,6 +106,7 @@
        DataView<String> parentsView = new DataView<String>("commitParents", parentsDp) {
            private static final long serialVersionUID = 1L;
            @Override
            public void populateItem(final Item<String> item) {
                String entry = item.getModelObject();
                item.add(new LinkPanel("commitParent", "list", entry, CommitPage.class,
@@ -126,6 +127,7 @@
        DataView<GitNote> notesView = new DataView<GitNote>("notes", notesDp) {
            private static final long serialVersionUID = 1L;
            @Override
            public void populateItem(final Item<GitNote> item) {
                GitNote entry = item.getModelObject();
                item.add(new RefsPanel("refName", repositoryName, Arrays.asList(entry.notesRef)));
@@ -142,7 +144,7 @@
        // changed paths list
        List<PathChangeModel> paths = JGitUtils.getFilesInCommit(r, c);
        // add commit diffstat
        int insertions = 0;
        int deletions = 0;
@@ -151,13 +153,14 @@
            deletions += pcm.deletions;
        }
        add(new DiffStatPanel("diffStat", insertions, deletions));
        add(new CommitLegendPanel("commitLegend", paths));
        ListDataProvider<PathChangeModel> pathsDp = new ListDataProvider<PathChangeModel>(paths);
        DataView<PathChangeModel> pathsView = new DataView<PathChangeModel>("changedPath", pathsDp) {
            private static final long serialVersionUID = 1L;
            int counter;
            @Override
            public void populateItem(final Item<PathChangeModel> item) {
                final PathChangeModel entry = item.getModelObject();
                Label changeType = new Label("changeType", "");
@@ -179,7 +182,7 @@
                    SubmoduleModel submodule = getSubmodule(entry.path);
                    submodulePath = submodule.gitblitPath;
                    hasSubmodule = submodule.hasSubmodule;
                    item.add(new LinkPanel("pathName", "list", entry.path + " @ " +
                            getShortObjectId(submoduleId), TreePage.class,
                            WicketUtils.newPathParameter(submodulePath, submoduleId, "")).setEnabled(hasSubmodule));
@@ -195,10 +198,10 @@
                            WicketUtils
                                    .newPathParameter(repositoryName, entry.commitId, path)));
                }
                // quick links
                if (entry.isSubmodule()) {
                    // submodule
                    // submodule
                    item.add(new BookmarkablePageLink<Void>("diff", BlobDiffPage.class, WicketUtils
                            .newPathParameter(repositoryName, entry.commitId, entry.path))
                            .setEnabled(!entry.changeType.equals(ChangeType.ADD)));
@@ -237,7 +240,7 @@
    protected String getPageName() {
        return getString("gb.commit");
    }
    @Override
    protected Class<? extends BasePage> getRepoNavPageClass() {
        return LogPage.class;
Diff truncated after the above file
src/main/java/com/gitblit/wicket/pages/ComparePage.java src/main/java/com/gitblit/wicket/pages/DashboardPage.java src/main/java/com/gitblit/wicket/pages/DocsPage.java src/main/java/com/gitblit/wicket/pages/EditRepositoryPage.java src/main/java/com/gitblit/wicket/pages/EditTeamPage.java src/main/java/com/gitblit/wicket/pages/EditUserPage.java src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java src/main/java/com/gitblit/wicket/pages/FederationRegistrationPage.java src/main/java/com/gitblit/wicket/pages/ForkPage.java src/main/java/com/gitblit/wicket/pages/ForksPage.java src/main/java/com/gitblit/wicket/pages/GitSearchPage.java src/main/java/com/gitblit/wicket/pages/GravatarProfilePage.java src/main/java/com/gitblit/wicket/pages/HistoryPage.java src/main/java/com/gitblit/wicket/pages/LogPage.java src/main/java/com/gitblit/wicket/pages/LogoutPage.java src/main/java/com/gitblit/wicket/pages/LuceneSearchPage.java src/main/java/com/gitblit/wicket/pages/MarkdownPage.java src/main/java/com/gitblit/wicket/pages/MetricsPage.java src/main/java/com/gitblit/wicket/pages/MyDashboardPage.java src/main/java/com/gitblit/wicket/pages/OverviewPage.java src/main/java/com/gitblit/wicket/pages/ProjectPage.java src/main/java/com/gitblit/wicket/pages/ProjectsPage.java src/main/java/com/gitblit/wicket/pages/RawPage.java src/main/java/com/gitblit/wicket/pages/ReflogPage.java src/main/java/com/gitblit/wicket/pages/RepositoriesPage.java src/main/java/com/gitblit/wicket/pages/ReviewProposalPage.java src/main/java/com/gitblit/wicket/pages/RootPage.java src/main/java/com/gitblit/wicket/pages/RootSubPage.java src/main/java/com/gitblit/wicket/pages/SendProposalPage.java src/main/java/com/gitblit/wicket/pages/SummaryPage.java src/main/java/com/gitblit/wicket/pages/TagPage.java src/main/java/com/gitblit/wicket/pages/TagsPage.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/UsersPage.java src/main/java/com/gitblit/wicket/panels/ActivityPanel.java src/main/java/com/gitblit/wicket/panels/BasePanel.java src/main/java/com/gitblit/wicket/panels/BranchesPanel.java src/main/java/com/gitblit/wicket/panels/BulletListPanel.java src/main/java/com/gitblit/wicket/panels/CommitLegendPanel.java src/main/java/com/gitblit/wicket/panels/CompressedDownloadsPanel.java src/main/java/com/gitblit/wicket/panels/DigestsPanel.java src/main/java/com/gitblit/wicket/panels/DropDownMenu.java src/main/java/com/gitblit/wicket/panels/FederationProposalsPanel.java src/main/java/com/gitblit/wicket/panels/FederationRegistrationsPanel.java src/main/java/com/gitblit/wicket/panels/FederationTokensPanel.java src/main/java/com/gitblit/wicket/panels/FilterableProjectList.java src/main/java/com/gitblit/wicket/panels/FilterableRepositoryList.java src/main/java/com/gitblit/wicket/panels/GravatarImage.java src/main/java/com/gitblit/wicket/panels/HistoryPanel.java src/main/java/com/gitblit/wicket/panels/LinkPanel.java src/main/java/com/gitblit/wicket/panels/LogPanel.java src/main/java/com/gitblit/wicket/panels/NavigationPanel.java src/main/java/com/gitblit/wicket/panels/ObjectContainer.java src/main/java/com/gitblit/wicket/panels/PagerPanel.java src/main/java/com/gitblit/wicket/panels/PathBreadcrumbsPanel.java src/main/java/com/gitblit/wicket/panels/ReflogPanel.java src/main/java/com/gitblit/wicket/panels/RefsPanel.java src/main/java/com/gitblit/wicket/panels/RegistrantPermissionsPanel.java 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/ShockWaveComponent.java src/main/java/com/gitblit/wicket/panels/TagsPanel.java src/main/java/com/gitblit/wicket/panels/TeamsPanel.java src/main/java/com/gitblit/wicket/panels/UsersPanel.java src/test/java/com/gitblit/tests/HtpasswdUserServiceTest.java src/test/java/com/gitblit/tests/JnaUtilsTest.java